From fb93b2bc8b91185f4755d1743c11c07592746148 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Sun, 31 Aug 2025 18:29:19 +0800 Subject: [PATCH 1/7] add p0 assert --- src/public/py_exception.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/public/py_exception.c b/src/public/py_exception.c index 3b97eac8..cd4267ba 100644 --- a/src/public/py_exception.c +++ b/src/public/py_exception.c @@ -158,12 +158,14 @@ bool py_matchexc(py_Type type) { void py_clearexc(py_StackRef p0) { VM* vm = pk_current_vm; py_newnil(&vm->unhandled_exc); - if(p0) vm->stack.sp = p0; + if(p0) { + c11__rtassert(p0 >= vm->stack.begin && p0 <= vm->stack.sp); + vm->stack.sp = p0; + } } static void c11_sbuf__write_exc(c11_sbuf* self, py_Ref exc) { - if(true) { c11_sbuf__write_cstr(self, "Traceback (most recent call last):\n"); } - + c11_sbuf__write_cstr(self, "Traceback (most recent call last):\n"); BaseException* ud = py_touserdata(exc); for(int i = ud->stacktrace.length - 1; i >= 0; i--) { From 897bc0be81740f1048736f414a1d855784f64049 Mon Sep 17 00:00:00 2001 From: lightovernight <119399319+lightovernight@users.noreply.github.com> Date: Sun, 31 Aug 2025 18:52:05 +0800 Subject: [PATCH 2/7] Fix bug : reference invalid (#393) * fix an fatal error * fix bug : rerefrence invaild * implement better exception support --- src/debugger/core.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/debugger/core.c b/src/debugger/core.c index 56432400..d4aeb485 100644 --- a/src/debugger/core.c +++ b/src/debugger/core.c @@ -114,6 +114,8 @@ void c11_debugger_exception_on_trace(py_Ref exc) { debugger.current_excname = name; debugger.current_excmessage = message; clear_structures(); + py_assign(py_list_getitem(python_vars, 0), exc); + py_clearexc(NULL); } const char* c11_debugger_excinfo(const char** message) { @@ -178,6 +180,8 @@ bool c11_debugger_path_equal(const char* path1, const char* path2) { C11_STOP_REASON c11_debugger_should_pause() { if(debugger.current_event == TRACE_EVENT_POP && !debugger.isexceptionmode) return C11_DEBUGGER_NOSTOP; + if(py_checkexc() && debugger.isexceptionmode == false) + return C11_DEBUGGER_NOSTOP; C11_STOP_REASON pause_resaon = C11_DEBUGGER_NOSTOP; int is_out = debugger.curr_stack_depth <= debugger.pause_allowed_depth; int is_new_line = debugger.current_line != debugger.step_line; @@ -282,8 +286,8 @@ inline static c11_debugger_scope_index append_new_scope(int frameid) { py_Frame* requested_frame = c11__getitem(py_Frame*, &debugger.py_frames, frameid); int base_index = py_list_len(python_vars); py_Ref new_locals = py_list_emplace(python_vars); - py_Ref new_globals = py_list_emplace(python_vars); py_Frame_newlocals(requested_frame, new_locals); + py_Ref new_globals = py_list_emplace(python_vars); py_Frame_newglobals(requested_frame, new_globals); c11_debugger_scope_index result = {.locals_ref = base_index, .globals_ref = base_index + 1}; return result; From b02e76b6904fe4bfa0fe92924357d5800aa7508b Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Sat, 6 Sep 2025 16:42:44 +0800 Subject: [PATCH 3/7] Update vmath.c --- src/modules/vmath.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/vmath.c b/src/modules/vmath.c index 428ea2e0..cec6bb65 100644 --- a/src/modules/vmath.c +++ b/src/modules/vmath.c @@ -1089,7 +1089,7 @@ static bool color32_alpha_blend_STATIC(int argc, py_Ref argv) { res.r = (unsigned char)(src.r * alpha + dst.r * (1 - alpha)); res.g = (unsigned char)(src.g * alpha + dst.g * (1 - alpha)); res.b = (unsigned char)(src.b * alpha + dst.b * (1 - alpha)); - res.a = (unsigned char)(src.a * alpha + dst.a * (1 - alpha)); + res.a = (unsigned char)(src.a + dst.a * (1 - alpha)); py_newcolor32(py_retval(), res); return true; } From d3d61dde0c5a6353f9d67abb8ae27c594365fa1b Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 8 Sep 2025 15:41:44 +0800 Subject: [PATCH 4/7] refactor code --- include/pocketpy/pocketpy.h | 734 +++++++++---------- src/{public => bindings}/py_array.c | 0 src/{public => bindings}/py_mappingproxy.c | 0 src/{public => bindings}/py_method.c | 0 src/{public => bindings}/py_number.c | 0 src/{public => bindings}/py_object.c | 0 src/{public => bindings}/py_property.c | 0 src/{public => bindings}/py_range.c | 0 src/{public => bindings}/py_str.c | 90 --- src/interpreter/ceval.c | 70 +- src/interpreter/frame.c | 41 -- src/interpreter/typeinfo.c | 63 +- src/{public/modules.c => modules/builtins.c} | 276 ------- src/modules/gc.c | 5 - src/modules/json.c | 6 - src/public/Bindings.c | 61 ++ src/public/{exec.c => CodeExecution.c} | 97 ++- src/public/{stack_ops.c => DictSlots.c} | 73 +- src/public/FrameOps.c | 43 ++ src/public/GlobalSetup.c | 161 ++++ src/public/Inspection.c | 28 + src/public/ModuleSystem.c | 191 +++++ src/public/{py_dict.c => PyDict.c} | 0 src/public/{py_exception.c => PyException.c} | 53 +- src/public/{py_list.c => PyList.c} | 0 src/public/{py_slice.c => PySlice.c} | 10 +- src/public/{py_tuple.c => PyTuple.c} | 0 src/public/{py_ops.c => PythonOps.c} | 90 ++- src/public/{internal.c => StackOps.c} | 189 ++--- src/public/TypeSystem.c | 104 +++ src/public/{cast.c => ValueCast.c} | 29 +- src/public/{values.c => ValueCreation.c} | 108 ++- src/public/typecast.c | 45 -- 33 files changed, 1296 insertions(+), 1271 deletions(-) rename src/{public => bindings}/py_array.c (100%) rename src/{public => bindings}/py_mappingproxy.c (100%) rename src/{public => bindings}/py_method.c (100%) rename src/{public => bindings}/py_number.c (100%) rename src/{public => bindings}/py_object.c (100%) rename src/{public => bindings}/py_property.c (100%) rename src/{public => bindings}/py_range.c (100%) rename src/{public => bindings}/py_str.c (88%) rename src/{public/modules.c => modules/builtins.c} (67%) create mode 100644 src/public/Bindings.c rename src/public/{exec.c => CodeExecution.c} (60%) rename src/public/{stack_ops.c => DictSlots.c} (56%) create mode 100644 src/public/FrameOps.c create mode 100644 src/public/GlobalSetup.c create mode 100644 src/public/Inspection.c create mode 100644 src/public/ModuleSystem.c rename src/public/{py_dict.c => PyDict.c} (100%) rename src/public/{py_exception.c => PyException.c} (98%) rename src/public/{py_list.c => PyList.c} (100%) rename src/public/{py_slice.c => PySlice.c} (91%) rename src/public/{py_tuple.c => PyTuple.c} (100%) rename src/public/{py_ops.c => PythonOps.c} (78%) rename src/public/{internal.c => StackOps.c} (54%) create mode 100644 src/public/TypeSystem.c rename src/public/{cast.c => ValueCast.c} (65%) rename src/public/{values.c => ValueCreation.c} (62%) delete mode 100644 src/public/typecast.c diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index ef57ef0d..02f618bf 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -123,14 +123,15 @@ PK_API void py_resetallvm(); PK_API void* py_getvmctx(); /// Set the current VM context. This is used for user-defined data. PK_API void py_setvmctx(void* ctx); +/// Setup the callbacks for the current VM. +PK_API py_Callbacks* py_callbacks(); + /// Set `sys.argv`. Used for storing command-line arguments. PK_API void py_sys_setargv(int argc, char** argv); /// Set the trace function for the current VM. PK_API void py_sys_settrace(py_TraceFunc func, bool reset); /// Invoke the garbage collector. PK_API int py_gc_collect(); -/// Setup the callbacks for the current VM. -PK_API py_Callbacks* py_callbacks(); /// Wrapper for `PK_MALLOC(size)`. PK_API void* py_malloc(size_t size); @@ -139,18 +140,16 @@ PK_API void* py_realloc(void* ptr, size_t size); /// Wrapper for `PK_FREE(ptr)`. PK_API void py_free(void* ptr); -/// Begin the watchdog with `timeout` in milliseconds. -/// `PK_ENABLE_WATCHDOG` must be defined to `1` to use this feature. -/// You need to call `py_watchdog_end()` later. -/// If `timeout` is reached, `TimeoutError` will be raised. -PK_API void py_watchdog_begin(py_i64 timeout); -/// Reset the watchdog. -PK_API void py_watchdog_end(); +/// A shorthand for `True`. +PK_API py_GlobalRef py_True(); +/// A shorthand for `False`. +PK_API py_GlobalRef py_False(); +/// A shorthand for `None`. +PK_API py_GlobalRef py_None(); +/// A shorthand for `nil`. `nil` is not a valid python object. +PK_API py_GlobalRef py_NIL(); -/// Bind a compile-time function via "decl-based" style. -PK_API void py_macrobind(const char* sig, py_CFunction f); -/// Get a compile-time function by name. -PK_API py_ItemRef py_macroget(py_Name name); +/************* Frame Ops *************/ /// Get the current source location of the frame. PK_API const char* py_Frame_sourceloc(py_Frame* frame, int* lineno); @@ -162,6 +161,14 @@ PK_API void py_Frame_newlocals(py_Frame* frame, py_OutRef out); /// Returns `NULL` if not available. PK_API py_StackRef py_Frame_function(py_Frame* frame); +/************* Code Execution *************/ + +/// Compile a source string into a code object. +/// Use python's `exec()` or `eval()` to execute it. +PK_API bool py_compile(const char* source, + const char* filename, + enum py_CompileMode mode, + bool is_dynamic) PY_RAISE PY_RETURN; /// Run a source string. /// @param source source string. /// @param filename filename (for error messages). @@ -172,10 +179,8 @@ PK_API bool py_exec(const char* source, const char* filename, enum py_CompileMode mode, py_Ref module) PY_RAISE PY_RETURN; - /// Evaluate a source string. Equivalent to `py_exec(source, "", EVAL_MODE, module)`. PK_API bool py_eval(const char* source, py_Ref module) PY_RAISE PY_RETURN; - /// Run a source string with smart interpretation. /// Example: /// `py_newstr(py_r0(), "abc");` @@ -191,28 +196,7 @@ PK_API bool py_smartexec(const char* source, py_Ref module, ...) PY_RAISE PY_RET /// `// res will be 3`. PK_API bool py_smarteval(const char* source, py_Ref module, ...) PY_RAISE PY_RETURN; -/// Compile a source string into a code object. -/// Use python's `exec()` or `eval()` to execute it. -PK_API bool py_compile(const char* source, - const char* filename, - enum py_CompileMode mode, - bool is_dynamic) PY_RAISE PY_RETURN; - -/// Python equivalent to `globals()`. -PK_API void py_newglobals(py_OutRef); -/// Python equivalent to `locals()`. -PK_API void py_newlocals(py_OutRef); - -/************* Values Creation *************/ - -/// A shorthand for `True`. -PK_API py_GlobalRef py_True(); -/// A shorthand for `False`. -PK_API py_GlobalRef py_False(); -/// A shorthand for `None`. -PK_API py_GlobalRef py_None(); -/// A shorthand for `nil`. `nil` is not a valid python object. -PK_API py_GlobalRef py_NIL(); +/************* Value Creation *************/ /// Create an `int` object. PK_API void py_newint(py_OutRef, py_i64); @@ -241,19 +225,6 @@ PK_API void py_newellipsis(py_OutRef); /// Create a `nil` object. `nil` is an invalid representation of an object. /// Don't use it unless you know what you are doing. PK_API void py_newnil(py_OutRef); -/// Create a `tuple` with `n` UNINITIALIZED elements. -/// You should initialize all elements before using it. -PK_API py_ObjectRef py_newtuple(py_OutRef, int n); -/// Create an empty `list`. -PK_API void py_newlist(py_OutRef); -/// Create a `list` with `n` UNINITIALIZED elements. -/// You should initialize all elements before using it. -PK_API void py_newlistn(py_OutRef, int n); -/// Create an empty `dict`. -PK_API void py_newdict(py_OutRef); -/// Create an UNINITIALIZED `slice` object. -/// You should use `py_setslot()` to set `start`, `stop`, and `step`. -PK_API void py_newslice(py_OutRef); /// Create a `nativefunc` object. PK_API void py_newnativefunc(py_OutRef, py_CFunction); /// Create a `function` object. @@ -264,8 +235,15 @@ PK_API py_Name py_newfunction(py_OutRef out, int slots); /// Create a `boundmethod` object. PK_API void py_newboundmethod(py_OutRef out, py_Ref self, py_Ref func); +/// Create a new object. +/// @param out output reference. +/// @param type type of the object. +/// @param slots number of slots. Use `-1` to create a `__dict__`. +/// @param udsize size of your userdata. +/// @return pointer to the userdata. +PK_API void* py_newobject(py_OutRef out, py_Type type, int slots, int udsize); -/************* Name Conversions *************/ +/************* Name Conversion *************/ /// Convert a null-terminated string to a name. PK_API py_Name py_name(const char*); @@ -278,184 +256,6 @@ PK_API py_Name py_namev(c11_sv); /// Convert a name to a `c11_sv`. PK_API c11_sv py_name2sv(py_Name); -/************* Meta Operations *************/ - -/// Create a new type. -/// @param name name of the type. -/// @param base base type. -/// @param module module where the type is defined. Use `NULL` for built-in types. -/// @param dtor destructor function. Use `NULL` if not needed. -PK_API py_Type py_newtype(const char* name, py_Type base, const py_GlobalRef module, py_Dtor dtor); - -/// Create a new object. -/// @param out output reference. -/// @param type type of the object. -/// @param slots number of slots. Use `-1` to create a `__dict__`. -/// @param udsize size of your userdata. -/// @return pointer to the userdata. -PK_API void* py_newobject(py_OutRef out, py_Type type, int slots, int udsize); - -/************* Type Cast *************/ - -/// Convert an `int` object in python to `int64_t`. -PK_API py_i64 py_toint(py_Ref); -/// Get the address of the trivial value object. -PK_API void* py_totrivial(py_Ref); -/// Convert a `float` object in python to `double`. -PK_API py_f64 py_tofloat(py_Ref); -/// Cast a `int` or `float` object in python to `double`. -/// If successful, return true and set the value to `out`. -/// Otherwise, return false and raise `TypeError`. -PK_API bool py_castfloat(py_Ref, py_f64* out) PY_RAISE; -/// 32-bit version of `py_castfloat`. -PK_API bool py_castfloat32(py_Ref, float* out) PY_RAISE; -/// Cast a `int` object in python to `int64_t`. -PK_API bool py_castint(py_Ref, py_i64* out) PY_RAISE; -/// Convert a `bool` object in python to `bool`. -PK_API bool py_tobool(py_Ref); -/// Convert a `type` object in python to `py_Type`. -PK_API py_Type py_totype(py_Ref); -/// Convert a `str` object in python to null-terminated string. -PK_API const char* py_tostr(py_Ref); -/// Convert a `str` object in python to char array. -PK_API const char* py_tostrn(py_Ref, int* size); -/// Convert a `str` object in python to `c11_sv`. -PK_API c11_sv py_tosv(py_Ref); -/// Convert a `bytes` object in python to char array. -PK_API unsigned char* py_tobytes(py_Ref, int* size); -/// Resize a `bytes` object. It can only be resized down. -PK_API void py_bytes_resize(py_Ref, int size); -/// Convert a user-defined object to its userdata. -PK_API void* py_touserdata(py_Ref); - -#define py_isint(self) py_istype(self, tp_int) -#define py_isfloat(self) py_istype(self, tp_float) -#define py_isbool(self) py_istype(self, tp_bool) -#define py_isstr(self) py_istype(self, tp_str) -#define py_islist(self) py_istype(self, tp_list) -#define py_istuple(self) py_istype(self, tp_tuple) -#define py_isdict(self) py_istype(self, tp_dict) - -#define py_isnil(self) py_istype(self, 0) -#define py_isnone(self) py_istype(self, tp_NoneType) - -/// Get the type of the object. -PK_API py_Type py_typeof(py_Ref self); -/// Get type by module and name. e.g. `py_gettype("time", py_name("struct_time"))`. -/// Return `0` if not found. -PK_API py_Type py_gettype(const char* module, py_Name name); -/// Check if the object is exactly the given type. -PK_API bool py_istype(py_Ref, py_Type); -/// Check if the object is an instance of the given type. -PK_API bool py_isinstance(py_Ref obj, py_Type type); -/// Check if the derived type is a subclass of the base type. -PK_API bool py_issubclass(py_Type derived, py_Type base); - -/// Get the magic method from the given type only. -/// Return `nil` if not found. -PK_API PK_DEPRECATED py_GlobalRef py_tpgetmagic(py_Type type, py_Name name); -/// Search the magic method from the given type to the base type. -/// Return `NULL` if not found. -PK_API py_GlobalRef py_tpfindmagic(py_Type, py_Name name); -/// Search the name from the given type to the base type. -/// Return `NULL` if not found. -PK_API py_ItemRef py_tpfindname(py_Type, py_Name name); -/// Get the base type of the given type. -PK_API py_Type py_tpbase(py_Type type); - -/// Get the type object of the given type. -PK_API py_GlobalRef py_tpobject(py_Type type); -/// Get the type name. -PK_API const char* py_tpname(py_Type type); -/// Call a type to create a new instance. -PK_API bool py_tpcall(py_Type type, int argc, py_Ref argv) PY_RAISE PY_RETURN; -/// Disable the type for subclassing. -PK_API void py_tpsetfinal(py_Type type); -/// Set attribute hooks for the given type. -PK_API void py_tphookattributes(py_Type type, - bool (*getattribute)(py_Ref self, py_Name name) PY_RAISE PY_RETURN, - bool (*setattribute)(py_Ref self, py_Name name, py_Ref val) - PY_RAISE PY_RETURN, - bool (*delattribute)(py_Ref self, py_Name name) PY_RAISE, - bool (*getunboundmethod)(py_Ref self, py_Name name) PY_RETURN); - -/// Check if the object is an instance of the given type exactly. -/// Raise `TypeError` if the check fails. -PK_API bool py_checktype(py_Ref self, py_Type type) PY_RAISE; - -/// Check if the object is an instance of the given type or its subclass. -/// Raise `TypeError` if the check fails. -PK_API bool py_checkinstance(py_Ref self, py_Type type) PY_RAISE; - -#define py_checkint(self) py_checktype(self, tp_int) -#define py_checkfloat(self) py_checktype(self, tp_float) -#define py_checkbool(self) py_checktype(self, tp_bool) -#define py_checkstr(self) py_checktype(self, tp_str) - -/************* References *************/ - -/// Get the i-th register. -/// All registers are located in a contiguous memory. -PK_API py_GlobalRef py_getreg(int i); -/// Set the i-th register. -PK_API void py_setreg(int i, py_Ref val); - -#define py_r0() py_getreg(0) -#define py_r1() py_getreg(1) -#define py_r2() py_getreg(2) -#define py_r3() py_getreg(3) -#define py_r4() py_getreg(4) -#define py_r5() py_getreg(5) -#define py_r6() py_getreg(6) -#define py_r7() py_getreg(7) - -/// Get variable in the `__main__` module. -PK_API py_ItemRef py_getglobal(py_Name name); -/// Set variable in the `__main__` module. -PK_API void py_setglobal(py_Name name, py_Ref val); -/// Get variable in the `builtins` module. -PK_API py_ItemRef py_getbuiltin(py_Name name); - -/// Get the last return value. -/// Please note that `py_retval()` cannot be used as input argument. -PK_API py_GlobalRef py_retval(); - -/// Get an item from the object's `__dict__`. -/// Return `NULL` if not found. -PK_API py_ItemRef py_getdict(py_Ref self, py_Name name); -/// Set an item to the object's `__dict__`. -PK_API void py_setdict(py_Ref self, py_Name name, py_Ref val); -/// Delete an item from the object's `__dict__`. -/// Return `true` if the deletion is successful. -PK_API bool py_deldict(py_Ref self, py_Name name); -/// Prepare an insertion to the object's `__dict__`. -PK_API py_ItemRef py_emplacedict(py_Ref self, py_Name name); -/// Apply a function to all items in the object's `__dict__`. -/// Return `true` if the function is successful for all items. -/// NOTE: Be careful if `f` modifies the object's `__dict__`. -PK_API bool - py_applydict(py_Ref self, bool (*f)(py_Name name, py_Ref val, void* ctx), void* ctx) PY_RAISE; -/// Clear the object's `__dict__`. This function is dangerous. -PK_API void py_cleardict(py_Ref self); - -/// Get the i-th slot of the object. -/// The object must have slots and `i` must be in valid range. -PK_API py_ObjectRef py_getslot(py_Ref self, int i); -/// Set the i-th slot of the object. -PK_API void py_setslot(py_Ref self, int i, py_Ref val); - -/************* Inspection *************/ - -/// Get the current `function` object on the stack. -/// Return `NULL` if not available. -/// NOTE: This function should be placed at the beginning of your decl-based bindings. -PK_API py_StackRef py_inspect_currentfunction(); -/// Get the current `module` object where the code is executed. -/// Return `NULL` if not available. -PK_API py_GlobalRef py_inspect_currentmodule(); -/// Get the current frame object. -/// Return `NULL` if not available. -PK_API py_Frame* py_inspect_currentframe(); /************* Bindings *************/ /// Bind a function to the object via "decl-based" style. @@ -487,6 +287,225 @@ PK_API void py_bindproperty(py_Type type, const char* name, py_CFunction getter, py_CFunction setter); /// Bind a magic method to type. PK_API void py_bindmagic(py_Type type, py_Name name, py_CFunction f); +/// Bind a compile-time function via "decl-based" style. +PK_API void py_macrobind(const char* sig, py_CFunction f); +/// Get a compile-time function by name. +PK_API py_ItemRef py_macroget(py_Name name); + +/************* Value Cast *************/ + +/// Convert an `int` object in python to `int64_t`. +PK_API py_i64 py_toint(py_Ref); +/// Get the address of the trivial value object (16 bytes). +PK_API void* py_totrivial(py_Ref); +/// Convert a `float` object in python to `double`. +PK_API py_f64 py_tofloat(py_Ref); +/// Cast a `int` or `float` object in python to `double`. +/// If successful, return true and set the value to `out`. +/// Otherwise, return false and raise `TypeError`. +PK_API bool py_castfloat(py_Ref, py_f64* out) PY_RAISE; +/// 32-bit version of `py_castfloat`. +PK_API bool py_castfloat32(py_Ref, float* out) PY_RAISE; +/// Cast a `int` object in python to `int64_t`. +PK_API bool py_castint(py_Ref, py_i64* out) PY_RAISE; +/// Convert a `bool` object in python to `bool`. +PK_API bool py_tobool(py_Ref); +/// Convert a `type` object in python to `py_Type`. +PK_API py_Type py_totype(py_Ref); +/// Convert a user-defined object to its userdata. +PK_API void* py_touserdata(py_Ref); +/// Convert a `str` object in python to null-terminated string. +PK_API const char* py_tostr(py_Ref); +/// Convert a `str` object in python to char array. +PK_API const char* py_tostrn(py_Ref, int* size); +/// Convert a `str` object in python to `c11_sv`. +PK_API c11_sv py_tosv(py_Ref); +/// Convert a `bytes` object in python to char array. +PK_API unsigned char* py_tobytes(py_Ref, int* size); +/// Resize a `bytes` object. It can only be resized down. +PK_API void py_bytes_resize(py_Ref, int size); + +/************* Type System *************/ + +/// Create a new type. +/// @param name name of the type. +/// @param base base type. +/// @param module module where the type is defined. Use `NULL` for built-in types. +/// @param dtor destructor function. Use `NULL` if not needed. +PK_API py_Type py_newtype(const char* name, py_Type base, const py_GlobalRef module, py_Dtor dtor); +/// Check if the object is exactly the given type. +PK_API bool py_istype(py_Ref, py_Type); +/// Get the type of the object. +PK_API py_Type py_typeof(py_Ref self); +/// Check if the object is an instance of the given type. +PK_API bool py_isinstance(py_Ref obj, py_Type type); +/// Check if the derived type is a subclass of the base type. +PK_API bool py_issubclass(py_Type derived, py_Type base); +/// Get type by module and name. e.g. `py_gettype("time", py_name("struct_time"))`. +/// Return `0` if not found. +PK_API py_Type py_gettype(const char* module, py_Name name); +/// Check if the object is an instance of the given type exactly. +/// Raise `TypeError` if the check fails. +PK_API bool py_checktype(py_Ref self, py_Type type) PY_RAISE; +/// Check if the object is an instance of the given type or its subclass. +/// Raise `TypeError` if the check fails. +PK_API bool py_checkinstance(py_Ref self, py_Type type) PY_RAISE; +/// Get the magic method from the given type only. +/// Return `nil` if not found. +PK_API PK_DEPRECATED py_GlobalRef py_tpgetmagic(py_Type type, py_Name name); +/// Search the magic method from the given type to the base type. +/// Return `NULL` if not found. +PK_API py_GlobalRef py_tpfindmagic(py_Type, py_Name name); +/// Search the name from the given type to the base type. +/// Return `NULL` if not found. +PK_API py_ItemRef py_tpfindname(py_Type, py_Name name); +/// Get the base type of the given type. +PK_API py_Type py_tpbase(py_Type type); +/// Get the type object of the given type. +PK_API py_GlobalRef py_tpobject(py_Type type); +/// Get the type name. +PK_API const char* py_tpname(py_Type type); +/// Disable the type for subclassing. +PK_API void py_tpsetfinal(py_Type type); +/// Set attribute hooks for the given type. +PK_API void py_tphookattributes(py_Type type, + bool (*getattribute)(py_Ref self, py_Name name) PY_RAISE PY_RETURN, + bool (*setattribute)(py_Ref self, py_Name name, py_Ref val) + PY_RAISE PY_RETURN, + bool (*delattribute)(py_Ref self, py_Name name) PY_RAISE, + bool (*getunboundmethod)(py_Ref self, py_Name name) PY_RETURN); + +#define py_isint(self) py_istype(self, tp_int) +#define py_isfloat(self) py_istype(self, tp_float) +#define py_isbool(self) py_istype(self, tp_bool) +#define py_isstr(self) py_istype(self, tp_str) +#define py_islist(self) py_istype(self, tp_list) +#define py_istuple(self) py_istype(self, tp_tuple) +#define py_isdict(self) py_istype(self, tp_dict) +#define py_isnil(self) py_istype(self, 0) +#define py_isnone(self) py_istype(self, tp_NoneType) + +#define py_checkint(self) py_checktype(self, tp_int) +#define py_checkfloat(self) py_checktype(self, tp_float) +#define py_checkbool(self) py_checktype(self, tp_bool) +#define py_checkstr(self) py_checktype(self, tp_str) + +/************* Inspection *************/ + +/// Get the current `function` object on the stack. +/// Return `NULL` if not available. +/// NOTE: This function should be placed at the beginning of your decl-based bindings. +PK_API py_StackRef py_inspect_currentfunction(); +/// Get the current `module` object where the code is executed. +/// Return `NULL` if not available. +PK_API py_GlobalRef py_inspect_currentmodule(); +/// Get the current frame object. +/// Return `NULL` if not available. +PK_API py_Frame* py_inspect_currentframe(); +/// Python equivalent to `globals()`. +PK_API void py_newglobals(py_OutRef); +/// Python equivalent to `locals()`. +PK_API void py_newlocals(py_OutRef); + +/************* Dict & Slots *************/ + +/// Get the i-th register. +/// All registers are located in a contiguous memory. +PK_API py_GlobalRef py_getreg(int i); +/// Set the i-th register. +PK_API void py_setreg(int i, py_Ref val); +/// Get the last return value. +/// Please note that `py_retval()` cannot be used as input argument. +PK_API py_GlobalRef py_retval(); + +#define py_r0() py_getreg(0) +#define py_r1() py_getreg(1) +#define py_r2() py_getreg(2) +#define py_r3() py_getreg(3) +#define py_r4() py_getreg(4) +#define py_r5() py_getreg(5) +#define py_r6() py_getreg(6) +#define py_r7() py_getreg(7) + +/// Get an item from the object's `__dict__`. +/// Return `NULL` if not found. +PK_API py_ItemRef py_getdict(py_Ref self, py_Name name); +/// Set an item to the object's `__dict__`. +PK_API void py_setdict(py_Ref self, py_Name name, py_Ref val); +/// Delete an item from the object's `__dict__`. +/// Return `true` if the deletion is successful. +PK_API bool py_deldict(py_Ref self, py_Name name); +/// Prepare an insertion to the object's `__dict__`. +PK_API py_ItemRef py_emplacedict(py_Ref self, py_Name name); +/// Apply a function to all items in the object's `__dict__`. +/// Return `true` if the function is successful for all items. +/// NOTE: Be careful if `f` modifies the object's `__dict__`. +PK_API bool + py_applydict(py_Ref self, bool (*f)(py_Name name, py_Ref val, void* ctx), void* ctx) PY_RAISE; +/// Clear the object's `__dict__`. This function is dangerous. +PK_API void py_cleardict(py_Ref self); +/// Get the i-th slot of the object. +/// The object must have slots and `i` must be in valid range. +PK_API py_ObjectRef py_getslot(py_Ref self, int i); +/// Set the i-th slot of the object. +PK_API void py_setslot(py_Ref self, int i, py_Ref val); +/// Get variable in the `builtins` module. +PK_API py_ItemRef py_getbuiltin(py_Name name); +/// Get variable in the `__main__` module. +PK_API py_ItemRef py_getglobal(py_Name name); +/// Set variable in the `__main__` module. +PK_API void py_setglobal(py_Name name, py_Ref val); + +/************* Stack Ops *************/ + +/// Get the i-th object from the top of the stack. +/// `i` should be negative, e.g. (-1) means TOS. +PK_API py_StackRef py_peek(int i); +/// Push the object to the stack. +PK_API void py_push(py_Ref src); +/// Push a `nil` object to the stack. +PK_API void py_pushnil(); +/// Push a `None` object to the stack. +PK_API void py_pushnone(); +/// Push a `py_Name` to the stack. This is used for keyword arguments. +PK_API void py_pushname(py_Name name); +/// Pop an object from the stack. +PK_API void py_pop(); +/// Shrink the stack by n. +PK_API void py_shrink(int n); +/// Get a temporary variable from the stack. +PK_API py_StackRef py_pushtmp(); +/// Get the unbound method of the object. +/// Assume the object is located at the top of the stack. +/// If return true: `[self] -> [unbound, self]`. +/// If return false: `[self] -> [self]` (no change). +PK_API bool py_pushmethod(py_Name name); +/// Evaluate an expression and push the result to the stack. +/// This function is used for testing. +PK_API bool py_pusheval(const char* expr, py_GlobalRef module) PY_RAISE; +/// Call a callable object via pocketpy's calling convention. +/// You need to prepare the stack using the following format: +/// `callable, self/nil, arg1, arg2, ..., k1, v1, k2, v2, ...`. +/// `argc` is the number of positional arguments excluding `self`. +/// `kwargc` is the number of keyword arguments. +/// The result will be set to `py_retval()`. +/// The stack size will be reduced by `2 + argc + kwargc * 2`. +PK_API bool py_vectorcall(uint16_t argc, uint16_t kwargc) PY_RAISE PY_RETURN; +/// Call a function. +/// It prepares the stack and then performs a `vectorcall(argc, 0, false)`. +/// The result will be set to `py_retval()`. +/// The stack remains unchanged if successful. +PK_API bool py_call(py_Ref f, int argc, py_Ref argv) PY_RAISE PY_RETURN; +/// Call a type to create a new instance. +PK_API bool py_tpcall(py_Type type, int argc, py_Ref argv) PY_RAISE PY_RETURN; + +#ifndef NDEBUG +/// Call a `py_CFunction` in a safe way. +/// This function does extra checks to help you debug `py_CFunction`. +PK_API bool py_callcfunc(py_CFunction f, int argc, py_Ref argv) PY_RAISE PY_RETURN; +#else +#define py_callcfunc(f, argc, argv) (f((argc), (argv))) +#endif #define PY_CHECK_ARGC(n) \ if(argc != n) return TypeError("expected %d arguments, got %d", n, argc) @@ -498,25 +517,13 @@ PK_API void py_bindmagic(py_Type type, py_Name name, py_CFunction f); #define py_arg(i) (&argv[i]) #define py_assign(dst, src) *(dst) = *(src) -/************* Python Equivalents *************/ - -/// Python equivalent to `getattr(self, name)`. -PK_API bool py_getattr(py_Ref self, py_Name name) PY_RAISE PY_RETURN; -/// Python equivalent to `setattr(self, name, val)`. -PK_API bool py_setattr(py_Ref self, py_Name name, py_Ref val) PY_RAISE; -/// Python equivalent to `delattr(self, name)`. -PK_API bool py_delattr(py_Ref self, py_Name name) PY_RAISE; -/// Python equivalent to `self[key]`. -PK_API bool py_getitem(py_Ref self, py_Ref key) PY_RAISE PY_RETURN; -/// Python equivalent to `self[key] = val`. -PK_API bool py_setitem(py_Ref self, py_Ref key, py_Ref val) PY_RAISE; -/// Python equivalent to `del self[key]`. -PK_API bool py_delitem(py_Ref self, py_Ref key) PY_RAISE; - /// Perform a binary operation. /// The result will be set to `py_retval()`. /// The stack remains unchanged after the operation. PK_API bool py_binaryop(py_Ref lhs, py_Ref rhs, py_Name op, py_Name rop) PY_RAISE PY_RETURN; + +/************* Python Ops *************/ + /// lhs + rhs PK_API bool py_binaryadd(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; /// lhs - rhs @@ -543,68 +550,74 @@ PK_API bool py_binaryor(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; PK_API bool py_binaryxor(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; /// lhs @ rhs PK_API bool py_binarymatmul(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; +/// lhs == rhs +PK_API bool py_eq(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; +/// lhs != rhs +PK_API bool py_ne(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; +/// lhs < rhs +PK_API bool py_lt(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; +/// lhs <= rhs +PK_API bool py_le(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; +/// lhs > rhs +PK_API bool py_gt(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; +/// lhs >= rhs +PK_API bool py_ge(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; -/************* Stack Operations *************/ +/// Python equivalent to `lhs is rhs`. +PK_API bool py_isidentical(py_Ref, py_Ref); +/// Python equivalent to `bool(val)`. +/// 1: true, 0: false, -1: error +PK_API int py_bool(py_Ref val) PY_RAISE; +/// Compare two objects. +/// 1: lhs == rhs, 0: lhs != rhs, -1: error +PK_API int py_equal(py_Ref lhs, py_Ref rhs) PY_RAISE; +/// Compare two objects. +/// 1: lhs < rhs, 0: lhs >= rhs, -1: error +PK_API int py_less(py_Ref lhs, py_Ref rhs) PY_RAISE; +/// Python equivalent to `callable(val)`. +PK_API bool py_callable(py_Ref val); +/// Get the hash value of the object. +PK_API bool py_hash(py_Ref, py_i64* out) PY_RAISE; +/// Get the iterator of the object. +PK_API bool py_iter(py_Ref) PY_RAISE PY_RETURN; +/// Get the next element from the iterator. +/// 1: success, 0: StopIteration, -1: error +PK_API int py_next(py_Ref) PY_RAISE PY_RETURN; +/// Python equivalent to `str(val)`. +PK_API bool py_str(py_Ref val) PY_RAISE PY_RETURN; +/// Python equivalent to `repr(val)`. +PK_API bool py_repr(py_Ref val) PY_RAISE PY_RETURN; +/// Python equivalent to `len(val)`. +PK_API bool py_len(py_Ref val) PY_RAISE PY_RETURN; -/// Get the i-th object from the top of the stack. -/// `i` should be negative, e.g. (-1) means TOS. -PK_API py_StackRef py_peek(int i); -/// Push the object to the stack. -PK_API void py_push(py_Ref src); -/// Push a `nil` object to the stack. -PK_API void py_pushnil(); -/// Push a `None` object to the stack. -PK_API void py_pushnone(); -/// Push a `py_Name` to the stack. This is used for keyword arguments. -PK_API void py_pushname(py_Name name); -/// Pop an object from the stack. -PK_API void py_pop(); -/// Shrink the stack by n. -PK_API void py_shrink(int n); -/// Get a temporary variable from the stack. -PK_API py_StackRef py_pushtmp(); -/// Get the unbound method of the object. -/// Assume the object is located at the top of the stack. -/// If return true: `[self] -> [unbound, self]`. -/// If return false: `[self] -> [self]` (no change). -PK_API bool py_pushmethod(py_Name name); -/// Call a callable object via pocketpy's calling convention. -/// You need to prepare the stack using the following format: -/// `callable, self/nil, arg1, arg2, ..., k1, v1, k2, v2, ...`. -/// `argc` is the number of positional arguments excluding `self`. -/// `kwargc` is the number of keyword arguments. -/// The result will be set to `py_retval()`. -/// The stack size will be reduced by `2 + argc + kwargc * 2`. -PK_API bool py_vectorcall(uint16_t argc, uint16_t kwargc) PY_RAISE PY_RETURN; -/// Evaluate an expression and push the result to the stack. -/// This function is used for testing. -PK_API bool py_pusheval(const char* expr, py_GlobalRef module) PY_RAISE; +/// Python equivalent to `getattr(self, name)`. +PK_API bool py_getattr(py_Ref self, py_Name name) PY_RAISE PY_RETURN; +/// Python equivalent to `setattr(self, name, val)`. +PK_API bool py_setattr(py_Ref self, py_Name name, py_Ref val) PY_RAISE; +/// Python equivalent to `delattr(self, name)`. +PK_API bool py_delattr(py_Ref self, py_Name name) PY_RAISE; +/// Python equivalent to `self[key]`. +PK_API bool py_getitem(py_Ref self, py_Ref key) PY_RAISE PY_RETURN; +/// Python equivalent to `self[key] = val`. +PK_API bool py_setitem(py_Ref self, py_Ref key, py_Ref val) PY_RAISE; +/// Python equivalent to `del self[key]`. +PK_API bool py_delitem(py_Ref self, py_Ref key) PY_RAISE; -/************* Modules *************/ +/************* Module System *************/ -/// Create a new module. -PK_API py_GlobalRef py_newmodule(const char* path); /// Get a module by path. PK_API py_GlobalRef py_getmodule(const char* path); +/// Create a new module. +PK_API py_GlobalRef py_newmodule(const char* path); /// Reload an existing module. PK_API bool py_importlib_reload(py_Ref module) PY_RAISE PY_RETURN; - /// Import a module. /// The result will be set to `py_retval()`. /// -1: error, 0: not found, 1: success PK_API int py_import(const char* path) PY_RAISE PY_RETURN; -/************* Errors *************/ +/************* PyException *************/ -/// Raise an exception by type and message. Always return false. -PK_API bool py_exception(py_Type type, const char* fmt, ...) PY_RAISE; -/// Raise an exception object. Always return false. -PK_API bool py_raise(py_Ref) PY_RAISE; -/// Print the unhandled exception. -PK_API void py_printexc(); -/// Format the unhandled exception and return a null-terminated string. -/// The returned string should be freed by the caller. -PK_API char* py_formatexc(); /// Check if there is an unhandled exception. PK_API bool py_checkexc(); /// Check if the unhandled exception is an instance of the given type. @@ -613,6 +626,15 @@ PK_API bool py_matchexc(py_Type type) PY_RETURN; /// Clear the unhandled exception. /// @param p0 the unwinding point. Use `NULL` if not needed. PK_API void py_clearexc(py_StackRef p0); +/// Print the unhandled exception. +PK_API void py_printexc(); +/// Format the unhandled exception and return a null-terminated string. +/// The returned string should be freed by the caller. +PK_API char* py_formatexc(); +/// Raise an exception by type and message. Always return false. +PK_API bool py_exception(py_Type type, const char* fmt, ...) PY_RAISE; +/// Raise an exception object. Always return false. +PK_API bool py_raise(py_Ref) PY_RAISE; #define NameError(n) py_exception(tp_NameError, "name '%n' is not defined", (n)) #define TypeError(...) py_exception(tp_TypeError, __VA_ARGS__) @@ -630,100 +652,40 @@ PK_API void py_clearexc(py_StackRef p0); "cannot access local variable '%n' where it is not associated with a value", \ (n)) -PK_API bool StopIteration() PY_RAISE; PK_API bool KeyError(py_Ref key) PY_RAISE; +PK_API bool StopIteration() PY_RAISE; -/************* Operators *************/ +/************* Debugger *************/ -/// Python equivalent to `bool(val)`. -/// 1: true, 0: false, -1: error -PK_API int py_bool(py_Ref val) PY_RAISE; -/// Compare two objects. -/// 1: lhs == rhs, 0: lhs != rhs, -1: error -PK_API int py_equal(py_Ref lhs, py_Ref rhs) PY_RAISE; -/// Compare two objects. -/// 1: lhs < rhs, 0: lhs >= rhs, -1: error -PK_API int py_less(py_Ref lhs, py_Ref rhs) PY_RAISE; - -/// lhs == rhs -PK_API bool py_eq(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; -/// lhs != rhs -PK_API bool py_ne(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; -/// lhs < rhs -PK_API bool py_lt(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; -/// lhs <= rhs -PK_API bool py_le(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; -/// lhs > rhs -PK_API bool py_gt(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; -/// lhs >= rhs -PK_API bool py_ge(py_Ref lhs, py_Ref rhs) PY_RAISE PY_RETURN; - -/// Python equivalent to `callable(val)`. -PK_API bool py_callable(py_Ref val); -/// Get the hash value of the object. -PK_API bool py_hash(py_Ref, py_i64* out) PY_RAISE; -/// Get the iterator of the object. -PK_API bool py_iter(py_Ref) PY_RAISE PY_RETURN; -/// Get the next element from the iterator. -/// 1: success, 0: StopIteration, -1: error -PK_API int py_next(py_Ref) PY_RAISE PY_RETURN; -/// Python equivalent to `lhs is rhs`. -PK_API bool py_isidentical(py_Ref, py_Ref); -/// Call a function. -/// It prepares the stack and then performs a `vectorcall(argc, 0, false)`. -/// The result will be set to `py_retval()`. -/// The stack remains unchanged if successful. -PK_API bool py_call(py_Ref f, int argc, py_Ref argv) PY_RAISE PY_RETURN; - -#ifndef NDEBUG -/// Call a `py_CFunction` in a safe way. -/// This function does extra checks to help you debug `py_CFunction`. -PK_API bool py_callcfunc(py_CFunction f, int argc, py_Ref argv) PY_RAISE PY_RETURN; -#else -#define py_callcfunc(f, argc, argv) (f((argc), (argv))) -#endif - -/// Python equivalent to `str(val)`. -PK_API bool py_str(py_Ref val) PY_RAISE PY_RETURN; -/// Python equivalent to `repr(val)`. -PK_API bool py_repr(py_Ref val) PY_RAISE PY_RETURN; -/// Python equivalent to `len(val)`. -PK_API bool py_len(py_Ref val) PY_RAISE PY_RETURN; -/// Python equivalent to `json.dumps(val)`. -PK_API bool py_json_dumps(py_Ref val, int indent) PY_RAISE PY_RETURN; -/// Python equivalent to `json.loads(val)`. -PK_API bool py_json_loads(const char* source) PY_RAISE PY_RETURN; -/// Python equivalent to `pickle.dumps(val)`. -PK_API bool py_pickle_dumps(py_Ref val) PY_RAISE PY_RETURN; -/// Python equivalent to `pickle.loads(val)`. -PK_API bool py_pickle_loads(const unsigned char* data, int size) PY_RAISE PY_RETURN; - -/************* Profiler *************/ -PK_API void py_profiler_begin(); -PK_API void py_profiler_end(); -PK_API void py_profiler_reset(); -PK_API char* py_profiler_report(); - -/************* DAP *************/ #if PK_ENABLE_OS PK_API void py_debugger_waitforattach(const char* hostname, unsigned short port); PK_API bool py_debugger_isattached(); PK_API void py_debugger_exceptionbreakpoint(py_Ref exc); -PK_API void py_debugger_exit(int exitCode); +PK_API void py_debugger_exit(int code); #else #define py_debugger_waitforattach(hostname, port) #define py_debugger_isattached() (false) #define py_debugger_exceptionbreakpoint(exc) -#define py_debugger_exit(exitCode) +#define py_debugger_exit(code) #endif -/************* Unchecked Functions *************/ +/************* PyTuple *************/ +/// Create a `tuple` with `n` UNINITIALIZED elements. +/// You should initialize all elements before using it. +PK_API py_ObjectRef py_newtuple(py_OutRef, int n); PK_API py_ObjectRef py_tuple_data(py_Ref self); PK_API py_ObjectRef py_tuple_getitem(py_Ref self, int i); PK_API void py_tuple_setitem(py_Ref self, int i, py_Ref val); PK_API int py_tuple_len(py_Ref self); +/************* PyList *************/ + +/// Create an empty `list`. +PK_API void py_newlist(py_OutRef); +/// Create a `list` with `n` UNINITIALIZED elements. +/// You should initialize all elements before using it. +PK_API void py_newlistn(py_OutRef, int n); PK_API py_ItemRef py_list_data(py_Ref self); PK_API py_ItemRef py_list_getitem(py_Ref self, int i); PK_API void py_list_setitem(py_Ref self, int i, py_Ref val); @@ -735,6 +697,10 @@ PK_API py_ItemRef py_list_emplace(py_Ref self); PK_API void py_list_clear(py_Ref self); PK_API void py_list_insert(py_Ref self, int i, py_Ref val); +/************* PyDict *************/ + +/// Create an empty `dict`. +PK_API void py_newdict(py_OutRef); /// -1: error, 0: not found, 1: found PK_API int py_dict_getitem(py_Ref self, py_Ref key) PY_RAISE PY_RETURN; /// true: success, false: error @@ -761,6 +727,14 @@ PK_API bool /// noexcept PK_API int py_dict_len(py_Ref self); +/************* PySlice *************/ + +/// Create an UNINITIALIZED `slice` object. +/// You should use `py_setslot()` to set `start`, `stop`, and `step`. +PK_API py_ObjectRef py_newslice(py_OutRef); +/// Create a `slice` object from 3 integers. +PK_API void py_newsliceint(py_OutRef out, py_i64 start, py_i64 stop, py_i64 step); + /************* random module *************/ PK_API void py_newRandom(py_OutRef out); PK_API void py_Random_seed(py_Ref self, py_i64 seed); @@ -789,6 +763,32 @@ PK_API c11_vec3i py_tovec3i(py_Ref self); PK_API c11_mat3x3* py_tomat3x3(py_Ref self); PK_API c11_color32 py_tocolor32(py_Ref self); +/************* json module *************/ +/// Python equivalent to `json.dumps(val)`. +PK_API bool py_json_dumps(py_Ref val, int indent) PY_RAISE PY_RETURN; +/// Python equivalent to `json.loads(val)`. +PK_API bool py_json_loads(const char* source) PY_RAISE PY_RETURN; + +/************* pickle module *************/ +/// Python equivalent to `pickle.dumps(val)`. +PK_API bool py_pickle_dumps(py_Ref val) PY_RAISE PY_RETURN; +/// Python equivalent to `pickle.loads(val)`. +PK_API bool py_pickle_loads(const unsigned char* data, int size) PY_RAISE PY_RETURN; + +/************* pkpy module *************/ +/// Begin the watchdog with `timeout` in milliseconds. +/// `PK_ENABLE_WATCHDOG` must be defined to `1` to use this feature. +/// You need to call `py_watchdog_end()` later. +/// If `timeout` is reached, `TimeoutError` will be raised. +PK_API void py_watchdog_begin(py_i64 timeout); +/// Reset the watchdog. +PK_API void py_watchdog_end(); + +PK_API void py_profiler_begin(); +PK_API void py_profiler_end(); +PK_API void py_profiler_reset(); +PK_API char* py_profiler_report(); + /************* Others *************/ /// An utility function to read a line from stdin for REPL. diff --git a/src/public/py_array.c b/src/bindings/py_array.c similarity index 100% rename from src/public/py_array.c rename to src/bindings/py_array.c diff --git a/src/public/py_mappingproxy.c b/src/bindings/py_mappingproxy.c similarity index 100% rename from src/public/py_mappingproxy.c rename to src/bindings/py_mappingproxy.c diff --git a/src/public/py_method.c b/src/bindings/py_method.c similarity index 100% rename from src/public/py_method.c rename to src/bindings/py_method.c diff --git a/src/public/py_number.c b/src/bindings/py_number.c similarity index 100% rename from src/public/py_number.c rename to src/bindings/py_number.c diff --git a/src/public/py_object.c b/src/bindings/py_object.c similarity index 100% rename from src/public/py_object.c rename to src/bindings/py_object.c diff --git a/src/public/py_property.c b/src/bindings/py_property.c similarity index 100% rename from src/public/py_property.c rename to src/bindings/py_property.c diff --git a/src/public/py_range.c b/src/bindings/py_range.c similarity index 100% rename from src/public/py_range.c rename to src/bindings/py_range.c diff --git a/src/public/py_str.c b/src/bindings/py_str.c similarity index 88% rename from src/public/py_str.c rename to src/bindings/py_str.c index c01f2c18..d142a521 100644 --- a/src/public/py_str.c +++ b/src/bindings/py_str.c @@ -1,59 +1,10 @@ #include "pocketpy/common/str.h" #include "pocketpy/pocketpy.h" -#include "pocketpy/common/utils.h" #include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" #include "pocketpy/common/sstream.h" -void py_newstr(py_OutRef out, const char* data) { py_newstrv(out, (c11_sv){data, strlen(data)}); } - -char* py_newstrn(py_OutRef out, int size) { - if(size < 16) { - out->type = tp_str; - out->is_ptr = false; - c11_string* ud = (c11_string*)(&out->extra); - c11_string__ctor3(ud, size); - return ud->data; - } - ManagedHeap* heap = &pk_current_vm->heap; - int total_size = sizeof(c11_string) + size + 1; - PyObject* obj = ManagedHeap__gcnew(heap, tp_str, 0, total_size); - c11_string* ud = PyObject__userdata(obj); - c11_string__ctor3(ud, size); - out->type = tp_str; - out->is_ptr = true; - out->_obj = obj; - return ud->data; -} - -void py_newstrv(py_OutRef out, c11_sv sv) { - char* data = py_newstrn(out, sv.size); - memcpy(data, sv.data, sv.size); -} - -void py_newfstr(py_OutRef out, const char* fmt, ...) { - c11_sbuf buf; - c11_sbuf__ctor(&buf); - va_list args; - va_start(args, fmt); - pk_vsprintf(&buf, fmt, args); - va_end(args); - c11_sbuf__py_submit(&buf, out); -} - -unsigned char* py_newbytes(py_OutRef out, int size) { - ManagedHeap* heap = &pk_current_vm->heap; - // 4 bytes size + data - PyObject* obj = ManagedHeap__gcnew(heap, tp_bytes, 0, sizeof(c11_bytes) + size); - c11_bytes* ud = PyObject__userdata(obj); - ud->size = size; - out->type = tp_bytes; - out->is_ptr = true; - out->_obj = obj; - return ud->data; -} - c11_string* pk_tostr(py_Ref self) { assert(self->type == tp_str); if(!self->is_ptr) { @@ -63,33 +14,6 @@ c11_string* pk_tostr(py_Ref self) { } } -const char* py_tostr(py_Ref self) { return pk_tostr(self)->data; } - -const char* py_tostrn(py_Ref self, int* size) { - c11_string* ud = pk_tostr(self); - *size = ud->size; - return ud->data; -} - -c11_sv py_tosv(py_Ref self) { - c11_string* ud = pk_tostr(self); - return c11_string__sv(ud); -} - -unsigned char* py_tobytes(py_Ref self, int* size) { - assert(self->type == tp_bytes); - c11_bytes* ud = PyObject__userdata(self->_obj); - *size = ud->size; - return ud->data; -} - -void py_bytes_resize(py_Ref self, int size) { - assert(self->type == tp_bytes); - c11_bytes* ud = PyObject__userdata(self->_obj); - if(size > ud->size) c11__abort("bytes can only be resized down: %d > %d", ud->size, size); - ud->size = size; -} - //////////////////////////////// static bool str__new__(int argc, py_Ref argv) { assert(argc >= 1); @@ -673,18 +597,4 @@ py_Type pk_bytes__register() { return type; } -bool py_str(py_Ref val) { - if(val->type == tp_str) { - py_assign(py_retval(), val); - return true; - } - py_Ref tmp = py_tpfindmagic(val->type, __str__); - if(!tmp) return py_repr(val); - return py_call(tmp, 1, val); -} - -bool py_repr(py_Ref val) { return pk_callmagic(__repr__, 1, val); } - -bool py_len(py_Ref val) { return pk_callmagic(__len__, 1, val); } - #undef DEF_STR_CMP_OP \ No newline at end of file diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 38e55d3d..492ca795 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -586,10 +586,10 @@ __NEXT_STEP: case OP_BUILD_SLICE: { // [start, stop, step] py_TValue tmp; - py_newslice(&tmp); - py_setslot(&tmp, 0, THIRD()); - py_setslot(&tmp, 1, SECOND()); - py_setslot(&tmp, 2, TOP()); + py_ObjectRef slots = py_newslice(&tmp); + slots[0] = *THIRD(); + slots[1] = *SECOND(); + slots[2] = *TOP(); STACK_SHRINK(3); PUSH(&tmp); DISPATCH(); @@ -1298,57 +1298,6 @@ bool pk_stack_binaryop(VM* self, py_Name op, py_Name rop) { rhs_t); } -bool py_binaryop(py_Ref lhs, py_Ref rhs, py_Name op, py_Name rop) { - VM* self = pk_current_vm; - PUSH(lhs); - PUSH(rhs); - bool ok = pk_stack_binaryop(self, op, rop); - STACK_SHRINK(2); - return ok; -} - -bool py_binaryadd(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __add__, __radd__); } - -bool py_binarysub(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __sub__, __rsub__); } - -bool py_binarymul(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __mul__, __rmul__); } - -bool py_binarytruediv(py_Ref lhs, py_Ref rhs) { - return py_binaryop(lhs, rhs, __truediv__, __rtruediv__); -} - -bool py_binaryfloordiv(py_Ref lhs, py_Ref rhs) { - return py_binaryop(lhs, rhs, __floordiv__, __rfloordiv__); -} - -bool py_binarymod(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __mod__, __rmod__); } - -bool py_binarypow(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __pow__, __rpow__); } - -bool py_binarylshift(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __lshift__, 0); } - -bool py_binaryrshift(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __rshift__, 0); } - -bool py_binaryand(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __and__, 0); } - -bool py_binaryor(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __or__, 0); } - -bool py_binaryxor(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __xor__, 0); } - -bool py_binarymatmul(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __matmul__, 0); } - -bool py_eq(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __eq__, __eq__); } - -bool py_ne(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __ne__, __ne__); } - -bool py_lt(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __lt__, __gt__); } - -bool py_le(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __le__, __ge__); } - -bool py_gt(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __gt__, __lt__); } - -bool py_ge(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __ge__, __le__); } - static bool stack_format_object(VM* self, c11_sv spec) { // format TOS via `spec` inplace // spec: '!r:.2f', '.2f' @@ -1514,14 +1463,3 @@ static bool stack_format_object(VM* self, c11_sv spec) { #undef INSERT_THIRD #undef vectorcall_opcall #undef RESET_CO_CACHE - -void py_sys_settrace(py_TraceFunc func, bool reset) { - TraceInfo* info = &pk_current_vm->trace_info; - info->func = func; - if(!reset) return; - if(info->prev_loc.src) { - PK_DECREF(info->prev_loc.src); - info->prev_loc.src = NULL; - } - info->prev_loc.lineno = -1; -} \ No newline at end of file diff --git a/src/interpreter/frame.c b/src/interpreter/frame.c index 78a93d79..3f2db232 100644 --- a/src/interpreter/frame.c +++ b/src/interpreter/frame.c @@ -162,44 +162,3 @@ SourceLocation Frame__source_location(py_Frame* self) { loc.src = self->co->src; return loc; } - -const char* py_Frame_sourceloc(py_Frame* self, int* lineno) { - SourceLocation loc = Frame__source_location(self); - *lineno = loc.lineno; - return loc.src->filename->data; -} - -void py_Frame_newglobals(py_Frame* frame, py_OutRef out) { - if(!frame) { - pk_mappingproxy__namedict(out, pk_current_vm->main); - return; - } - if(frame->globals->type == tp_module) { - pk_mappingproxy__namedict(out, frame->globals); - } else { - *out = *frame->globals; // dict - } -} - -void py_Frame_newlocals(py_Frame* frame, py_OutRef out) { - if(!frame) { - py_newdict(out); - return; - } - if(frame->is_locals_special) { - switch(frame->locals->type) { - case tp_locals: frame = frame->locals->_ptr; break; - case tp_dict: *out = *frame->locals; return; - case tp_nil: py_newdict(out); return; - default: c11__unreachable(); - } - } - FastLocals__to_dict(frame->locals, frame->co); - py_assign(out, py_retval()); -} - -py_StackRef py_Frame_function(py_Frame* self) { - if(self->is_locals_special) return NULL; - assert(self->p0->type == tp_function); - return self->p0; -} \ No newline at end of file diff --git a/src/interpreter/typeinfo.c b/src/interpreter/typeinfo.c index 93fba34d..a20b76ce 100644 --- a/src/interpreter/typeinfo.c +++ b/src/interpreter/typeinfo.c @@ -11,45 +11,11 @@ py_ItemRef pk_tpfindname(py_TypeInfo* ti, py_Name name) { return NULL; } -PK_INLINE py_ItemRef py_tpfindname(py_Type type, py_Name name) { - py_TypeInfo* ti = pk_typeinfo(type); - return pk_tpfindname(ti, name); -} - -PK_INLINE py_Ref py_tpfindmagic(py_Type t, py_Name name) { - // assert(py_ismagicname(name)); - return py_tpfindname(t, name); -} - -PK_INLINE py_Type py_tpbase(py_Type t) { - assert(t); - py_TypeInfo* ti = pk_typeinfo(t); - return ti->base; -} - -PK_DEPRECATED py_Ref py_tpgetmagic(py_Type type, py_Name name) { - // assert(py_ismagicname(name)); - py_TypeInfo* ti = pk_typeinfo(type); - py_Ref retval = py_getdict(&ti->self, name); - return retval != NULL ? retval : py_NIL(); -} - -py_Ref py_tpobject(py_Type type) { - assert(type); - return &pk_typeinfo(type)->self; -} - -const char* py_tpname(py_Type type) { - if(!type) return "nil"; - py_Name name = pk_typeinfo(type)->name; - return py_name2str(name); -} - PK_INLINE py_TypeInfo* pk_typeinfo(py_Type type) { #ifndef NDEBUG int length = pk_current_vm->types.length; - if(type < 0 || type >= length) { - c11__abort("type index %d is out of bounds [0, %d)", type, length); + if(type <= 0 || type >= length) { + c11__abort("type index %d is out of bounds (0, %d)", type, length); } #endif return c11__getitem(TypePointer, &pk_current_vm->types, type).ti; @@ -150,28 +116,3 @@ py_Type pk_newtypewithmode(py_Name name, return pk_newtype(py_name2str(name), base, module, dtor, is_python, is_final); } -py_Type py_newtype(const char* name, py_Type base, const py_GlobalRef module, void (*dtor)(void*)) { - if(strlen(name) == 0) c11__abort("type name cannot be empty"); - py_Type type = pk_newtype(name, base, module, dtor, false, false); - if(module) py_setdict(module, py_name(name), py_tpobject(type)); - return type; -} - -void py_tpsetfinal(py_Type type) { - assert(type); - py_TypeInfo* ti = pk_typeinfo(type); - ti->is_final = true; -} - -void py_tphookattributes(py_Type type, - bool (*getattribute)(py_Ref self, py_Name name), - bool (*setattribute)(py_Ref self, py_Name name, py_Ref val), - bool (*delattribute)(py_Ref self, py_Name name), - bool (*getunboundmethod)(py_Ref self, py_Name name)) { - assert(type); - py_TypeInfo* ti = pk_typeinfo(type); - ti->getattribute = getattribute; - ti->setattribute = setattribute; - ti->delattribute = delattribute; - ti->getunboundmethod = getunboundmethod; -} diff --git a/src/public/modules.c b/src/modules/builtins.c similarity index 67% rename from src/public/modules.c rename to src/modules/builtins.c index ccf740c9..8dc10e31 100644 --- a/src/public/modules.c +++ b/src/modules/builtins.c @@ -8,198 +8,8 @@ #include "pocketpy/interpreter/vm.h" #include "pocketpy/common/_generated.h" -#include #include -py_Ref py_getmodule(const char* path) { - VM* vm = pk_current_vm; - return BinTree__try_get(&vm->modules, (void*)path); -} - -py_Ref py_getbuiltin(py_Name name) { return py_getdict(pk_current_vm->builtins, name); } - -py_Ref py_getglobal(py_Name name) { return py_getdict(pk_current_vm->main, name); } - -void py_setglobal(py_Name name, py_Ref val) { py_setdict(pk_current_vm->main, name, val); } - -static void py_ModuleInfo__dtor(py_ModuleInfo* mi) { - c11_string__delete(mi->name); - c11_string__delete(mi->package); - c11_string__delete(mi->path); -} - -py_Type pk_module__register() { - py_Type type = pk_newtype("module", tp_object, NULL, (py_Dtor)py_ModuleInfo__dtor, false, true); - return type; -} - -py_Ref py_newmodule(const char* path) { - int path_len = strlen(path); - if(path_len > PK_MAX_MODULE_PATH_LEN) c11__abort("module path too long: %s", path); - if(path_len == 0) c11__abort("module path cannot be empty"); - - py_ModuleInfo* mi = py_newobject(py_retval(), tp_module, -1, sizeof(py_ModuleInfo)); - - int last_dot = c11_sv__rindex((c11_sv){path, path_len}, '.'); - if(last_dot == -1) { - mi->name = c11_string__new(path); - mi->package = c11_string__new(""); - } else { - const char* start = path + last_dot + 1; - mi->name = c11_string__new(start); - mi->package = c11_string__new2(path, last_dot); - } - - mi->path = c11_string__new(path); - path = mi->path->data; - - // we do not allow override in order to avoid memory leak - // it is because Module objects are not garbage collected - bool exists = BinTree__contains(&pk_current_vm->modules, (void*)path); - if(exists) c11__abort("module '%s' already exists", path); - - BinTree__set(&pk_current_vm->modules, (void*)path, py_retval()); - py_GlobalRef retval = py_getmodule(path); - mi->self = retval; - - // setup __name__ - py_newstrv(py_emplacedict(retval, __name__), c11_string__sv(mi->name)); - // setup __package__ - py_newstrv(py_emplacedict(retval, __package__), c11_string__sv(mi->package)); - // setup __path__ - py_newstrv(py_emplacedict(retval, __path__), c11_string__sv(mi->path)); - return retval; -} - -int load_module_from_dll_desktop_only(const char* path) PY_RAISE PY_RETURN; - -int py_import(const char* path_cstr) { - VM* vm = pk_current_vm; - c11_sv path = {path_cstr, strlen(path_cstr)}; - if(path.size == 0) return ValueError("empty module name"); - - if(path.data[0] == '.') { - // try relative import - int dot_count = 1; - while(dot_count < path.size && path.data[dot_count] == '.') - dot_count++; - - c11_sv top_filename = c11_string__sv(vm->top_frame->co->src->filename); - int is_init = c11_sv__endswith(top_filename, (c11_sv){"__init__.py", 11}); - - py_ModuleInfo* mi = py_touserdata(vm->top_frame->module); - c11_sv package_sv = c11_string__sv(mi->path); - if(package_sv.size == 0) { - return ImportError("attempted relative import with no known parent package"); - } - - c11_vector /* T=c11_sv */ cpnts = c11_sv__split(package_sv, '.'); - for(int i = is_init; i < dot_count; i++) { - if(cpnts.length == 0) - return ImportError("attempted relative import beyond top-level package"); - c11_vector__pop(&cpnts); - } - - if(dot_count < path.size) { - c11_sv last_cpnt = c11_sv__slice(path, dot_count); - c11_vector__push(c11_sv, &cpnts, last_cpnt); - } - - // join cpnts - c11_sbuf buf; - c11_sbuf__ctor(&buf); - for(int i = 0; i < cpnts.length; i++) { - if(i > 0) c11_sbuf__write_char(&buf, '.'); - c11_sbuf__write_sv(&buf, c11__getitem(c11_sv, &cpnts, i)); - } - - c11_vector__dtor(&cpnts); - c11_string* new_path = c11_sbuf__submit(&buf); - int res = py_import(new_path->data); - c11_string__delete(new_path); - return res; - } - - assert(path.data[0] != '.' && path.data[path.size - 1] != '.'); - - // check existing module - py_GlobalRef ext_mod = py_getmodule(path.data); - if(ext_mod) { - py_assign(py_retval(), ext_mod); - return true; - } - - if(vm->callbacks.lazyimport) { - py_GlobalRef lazymod = vm->callbacks.lazyimport(path_cstr); - if(lazymod) { - c11__rtassert(py_istype(lazymod, tp_module)); - py_assign(py_retval(), lazymod); - return 1; - } - } - - // try import - c11_string* slashed_path = c11_sv__replace(path, '.', PK_PLATFORM_SEP); - c11_string* filename = c11_string__new3("%s.py", slashed_path->data); - - bool need_free = true; - const char* data = load_kPythonLib(path_cstr); - if(data != NULL) { - need_free = false; - goto __SUCCESS; - } - - data = vm->callbacks.importfile(filename->data); - if(data != NULL) goto __SUCCESS; - - c11_string__delete(filename); - filename = c11_string__new3("%s%c__init__.py", slashed_path->data, PK_PLATFORM_SEP); - data = vm->callbacks.importfile(filename->data); - if(data != NULL) goto __SUCCESS; - - c11_string__delete(filename); - c11_string__delete(slashed_path); - // not found - return load_module_from_dll_desktop_only(path_cstr); - -__SUCCESS: - do { - } while(0); - py_GlobalRef mod = py_newmodule(path_cstr); - bool ok = py_exec((const char*)data, filename->data, EXEC_MODE, mod); - py_assign(py_retval(), mod); - - c11_string__delete(filename); - c11_string__delete(slashed_path); - if(need_free) PK_FREE((void*)data); - return ok ? 1 : -1; -} - -bool py_importlib_reload(py_Ref module) { - VM* vm = pk_current_vm; - py_ModuleInfo* mi = py_touserdata(module); - // We should ensure that the module is its original py_GlobalRef - module = mi->self; - c11_sv path = c11_string__sv(mi->path); - c11_string* slashed_path = c11_sv__replace(path, '.', PK_PLATFORM_SEP); - c11_string* filename = c11_string__new3("%s.py", slashed_path->data); - char* data = vm->callbacks.importfile(filename->data); - if(data == NULL) { - c11_string__delete(filename); - filename = c11_string__new3("%s%c__init__.py", slashed_path->data, PK_PLATFORM_SEP); - data = vm->callbacks.importfile(filename->data); - } - c11_string__delete(slashed_path); - if(data == NULL) return ImportError("module '%v' not found", path); - // py_cleardict(module); BUG: removing old classes will cause RELOAD_MODE to fail - bool ok = py_exec(data, filename->data, RELOAD_MODE, module); - c11_string__delete(filename); - PK_FREE(data); - py_assign(py_retval(), module); - return ok; -} - -////////////////////////// static bool builtins_exit(int argc, py_Ref argv) { int code = 0; @@ -399,18 +209,6 @@ static bool builtins_issubclass(int argc, py_Ref argv) { return true; } -bool py_callable(py_Ref val) { - switch(val->type) { - case tp_nativefunc: return true; - case tp_function: return true; - case tp_type: return true; - case tp_boundmethod: return true; - case tp_staticmethod: return true; - case tp_classmethod: return true; - default: return py_tpfindmagic(val->type, __call__); - } -} - static bool builtins_callable(int argc, py_Ref argv) { PY_CHECK_ARGC(1); bool res = py_callable(py_arg(0)); @@ -519,16 +317,6 @@ static bool builtins_locals(int argc, py_Ref argv) { return true; } -void py_newglobals(py_OutRef out) { - py_Frame* frame = pk_current_vm->top_frame; - py_Frame_newglobals(frame, out); -} - -void py_newlocals(py_OutRef out) { - py_Frame* frame = pk_current_vm->top_frame; - py_Frame_newlocals(frame, out); -} - static void pk_push_special_locals() { py_Frame* frame = pk_current_vm->top_frame; if(!frame) { @@ -622,70 +410,6 @@ static bool builtins_eval(int argc, py_Ref argv) { return _builtins_execdyn("eval", argc, argv, EVAL_MODE); } -static bool - pk_smartexec(const char* source, py_Ref module, enum py_CompileMode mode, va_list args) { - if(module == NULL) module = pk_current_vm->main; - pk_mappingproxy__namedict(py_pushtmp(), module); // globals - py_newdict(py_pushtmp()); // locals - bool ok = py_compile(source, "", mode, true); - if(!ok) return false; - py_push(py_retval()); - // [globals, locals, code] - CodeObject* co = py_touserdata(py_peek(-1)); - py_StackRef locals = py_peek(-2); - int max_index = -1; - c11__foreach(Bytecode, &co->codes, bc) { - if(bc->op == OP_LOAD_NAME) { - c11_sv name = py_name2sv(c11__getitem(py_Name, &co->names, bc->arg)); - if(name.data[0] != '_') continue; - int index; - if(name.size == 1) { - index = 0; - } else if(name.size == 2 && isdigit(name.data[1])) { - index = name.data[1] - '0'; - } else { - continue; - } - max_index = c11__max(max_index, index); - } - } - - if(max_index == -1) return ValueError("no placeholder found in the source"); - - for(int i = 0; i <= max_index; i++) { - py_Ref val = va_arg(args, py_Ref); - char buf[3]; - buf[0] = '_'; - buf[1] = '0' + i; - buf[2] = '\0'; - py_dict_setitem_by_str(locals, buf, val); - if(i == 0) { - // _ => _0 - py_dict_setitem_by_str(locals, "_", val); - } - } - ok = pk_execdyn(co, module, py_peek(-3), locals); - if(!ok) return false; - py_shrink(3); - return true; -} - -bool py_smartexec(const char* source, py_Ref module, ...) { - va_list args; - va_start(args, module); - bool ok = pk_smartexec(source, module, EXEC_MODE, args); - va_end(args); - return ok; -} - -bool py_smarteval(const char* source, py_Ref module, ...) { - va_list args; - va_start(args, module); - bool ok = pk_smartexec(source, module, EVAL_MODE, args); - va_end(args); - return ok; -} - static bool builtins_compile(int argc, py_Ref argv) { PY_CHECK_ARGC(3); for(int i = 0; i < 3; i++) { diff --git a/src/modules/gc.c b/src/modules/gc.c index 5c5b358f..6bb7fcb9 100644 --- a/src/modules/gc.c +++ b/src/modules/gc.c @@ -40,8 +40,3 @@ void pk__add_module_gc() { py_bindfunc(mod, "disable", gc_disable); py_bindfunc(mod, "isenabled", gc_isenabled); } - -int py_gc_collect() { - ManagedHeap* heap = &pk_current_vm->heap; - return ManagedHeap__collect(heap); -} \ No newline at end of file diff --git a/src/modules/json.c b/src/modules/json.c index e46fdd76..06d8b855 100644 --- a/src/modules/json.c +++ b/src/modules/json.c @@ -189,9 +189,3 @@ bool py_json_loads(const char* source) { return py_exec(source, "", EVAL_MODE, mod); } -bool py_pusheval(const char* expr, py_GlobalRef module) { - bool ok = py_exec(expr, "", EVAL_MODE, module); - if(!ok) return false; - py_push(py_retval()); - return true; -} diff --git a/src/public/Bindings.c b/src/public/Bindings.c new file mode 100644 index 00000000..28ca8950 --- /dev/null +++ b/src/public/Bindings.c @@ -0,0 +1,61 @@ +#include "pocketpy/interpreter/vm.h" + +void py_bind(py_Ref obj, const char* sig, py_CFunction f) { + py_Ref tmp = py_pushtmp(); + py_Name name = py_newfunction(tmp, sig, f, NULL, 0); + py_setdict(obj, name, tmp); + py_pop(); +} + +void py_bindmethod(py_Type type, const char* name, py_CFunction f) { + py_TValue tmp; + py_newnativefunc(&tmp, f); + py_setdict(py_tpobject(type), py_name(name), &tmp); +} + +void py_bindstaticmethod(py_Type type, const char* name, py_CFunction f) { + py_TValue tmp; + py_newnativefunc(&tmp, f); + bool ok = py_tpcall(tp_staticmethod, 1, &tmp); + if(!ok) { + py_printexc(); + c11__abort("py_bindstaticmethod(): failed to create staticmethod"); + } + py_setdict(py_tpobject(type), py_name(name), py_retval()); +} + +void py_bindfunc(py_Ref obj, const char* name, py_CFunction f) { + py_TValue tmp; + py_newnativefunc(&tmp, f); + py_setdict(obj, py_name(name), &tmp); +} + +void py_bindproperty(py_Type type, const char* name, py_CFunction getter, py_CFunction setter) { + py_TValue tmp; + py_newobject(&tmp, tp_property, 2, 0); + py_newnativefunc(py_getslot(&tmp, 0), getter); + if(setter) { + py_newnativefunc(py_getslot(&tmp, 1), setter); + } else { + py_setslot(&tmp, 1, py_None()); + } + py_setdict(py_tpobject(type), py_name(name), &tmp); +} + +void py_bindmagic(py_Type type, py_Name name, py_CFunction f) { + py_Ref tmp = py_emplacedict(py_tpobject(type), name); + py_newnativefunc(tmp, f); +} + +void py_macrobind(const char* sig, py_CFunction f) { + py_Ref tmp = py_pushtmp(); + py_Name name = py_newfunction(tmp, sig, f, NULL, 0); + NameDict__set(&pk_current_vm->compile_time_funcs, name, tmp); + py_pop(); +} + +py_ItemRef py_macroget(py_Name name) { + NameDict* d = &pk_current_vm->compile_time_funcs; + if(d->length == 0) return NULL; + return NameDict__try_get(d, name); +} diff --git a/src/public/exec.c b/src/public/CodeExecution.c similarity index 60% rename from src/public/exec.c rename to src/public/CodeExecution.c index 6a7f9a2a..f2a1a67f 100644 --- a/src/public/exec.c +++ b/src/public/CodeExecution.c @@ -6,13 +6,14 @@ #include "pocketpy/interpreter/vm.h" #include "pocketpy/compiler/compiler.h" #include +#include py_Type pk_code__register() { py_Type type = pk_newtype("code", tp_object, NULL, (py_Dtor)CodeObject__dtor, false, true); return type; } -bool _py_compile(CodeObject* out, +static bool _py_compile(CodeObject* out, const char* source, const char* filename, enum py_CompileMode mode, @@ -33,20 +34,6 @@ bool _py_compile(CodeObject* out, return true; } -bool py_compile(const char* source, - const char* filename, - enum py_CompileMode mode, - bool is_dynamic) { - CodeObject co; - bool ok = _py_compile(&co, source, filename, mode, is_dynamic); - if(ok) { - // compile success - CodeObject* ud = py_newobject(py_retval(), tp_code, 0, sizeof(CodeObject)); - *ud = co; - } - return ok; -} - bool pk_exec(CodeObject* co, py_Ref module) { VM* vm = pk_current_vm; if(!module) module = vm->main; @@ -92,6 +79,68 @@ bool pk_execdyn(CodeObject* co, py_Ref module, py_Ref globals, py_Ref locals) { return true; } +static bool + pk_smartexec(const char* source, py_Ref module, enum py_CompileMode mode, va_list args) { + if(module == NULL) module = pk_current_vm->main; + pk_mappingproxy__namedict(py_pushtmp(), module); // globals + py_newdict(py_pushtmp()); // locals + bool ok = py_compile(source, "", mode, true); + if(!ok) return false; + py_push(py_retval()); + // [globals, locals, code] + CodeObject* co = py_touserdata(py_peek(-1)); + py_StackRef locals = py_peek(-2); + int max_index = -1; + c11__foreach(Bytecode, &co->codes, bc) { + if(bc->op == OP_LOAD_NAME) { + c11_sv name = py_name2sv(c11__getitem(py_Name, &co->names, bc->arg)); + if(name.data[0] != '_') continue; + int index; + if(name.size == 1) { + index = 0; + } else if(name.size == 2 && isdigit(name.data[1])) { + index = name.data[1] - '0'; + } else { + continue; + } + max_index = c11__max(max_index, index); + } + } + + if(max_index == -1) return ValueError("no placeholder found in the source"); + + for(int i = 0; i <= max_index; i++) { + py_Ref val = va_arg(args, py_Ref); + char buf[3]; + buf[0] = '_'; + buf[1] = '0' + i; + buf[2] = '\0'; + py_dict_setitem_by_str(locals, buf, val); + if(i == 0) { + // _ => _0 + py_dict_setitem_by_str(locals, "_", val); + } + } + ok = pk_execdyn(co, module, py_peek(-3), locals); + if(!ok) return false; + py_shrink(3); + return true; +} + +bool py_compile(const char* source, + const char* filename, + enum py_CompileMode mode, + bool is_dynamic) { + CodeObject co; + bool ok = _py_compile(&co, source, filename, mode, is_dynamic); + if(ok) { + // compile success + CodeObject* ud = py_newobject(py_retval(), tp_code, 0, sizeof(CodeObject)); + *ud = co; + } + return ok; +} + bool py_exec(const char* source, const char* filename, enum py_CompileMode mode, py_Ref module) { CodeObject co; if(!_py_compile(&co, source, filename, mode, false)) return false; @@ -102,4 +151,20 @@ bool py_exec(const char* source, const char* filename, enum py_CompileMode mode, bool py_eval(const char* source, py_Ref module) { return py_exec(source, "", EVAL_MODE, module); -} \ No newline at end of file +} + +bool py_smartexec(const char* source, py_Ref module, ...) { + va_list args; + va_start(args, module); + bool ok = pk_smartexec(source, module, EXEC_MODE, args); + va_end(args); + return ok; +} + +bool py_smarteval(const char* source, py_Ref module, ...) { + va_list args; + va_start(args, module); + bool ok = pk_smartexec(source, module, EVAL_MODE, args); + va_end(args); + return ok; +} diff --git a/src/public/stack_ops.c b/src/public/DictSlots.c similarity index 56% rename from src/public/stack_ops.c rename to src/public/DictSlots.c index d5a60647..620df4fd 100644 --- a/src/public/stack_ops.c +++ b/src/public/DictSlots.c @@ -1,13 +1,11 @@ -#include "pocketpy/pocketpy.h" - -#include "pocketpy/common/utils.h" -#include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" py_Ref py_getreg(int i) { return pk_current_vm->reg + i; } void py_setreg(int i, py_Ref val) { pk_current_vm->reg[i] = *val; } +PK_INLINE py_Ref py_retval() { return &pk_current_vm->last_retval; } + PK_INLINE py_Ref py_getdict(py_Ref self, py_Name name) { assert(self && self->is_ptr); return NameDict__try_get(PyObject__dict(self->_obj), name); @@ -18,6 +16,11 @@ PK_INLINE void py_setdict(py_Ref self, py_Name name, py_Ref val) { NameDict__set(PyObject__dict(self->_obj), name, val); } +bool py_deldict(py_Ref self, py_Name name) { + assert(self && self->is_ptr); + return NameDict__del(PyObject__dict(self->_obj), name); +} + py_ItemRef py_emplacedict(py_Ref self, py_Name name) { py_setdict(self, name, py_NIL()); return py_getdict(self, name); @@ -41,11 +44,6 @@ void py_cleardict(py_Ref self) { NameDict__clear(dict); } -bool py_deldict(py_Ref self, py_Name name) { - assert(self && self->is_ptr); - return NameDict__del(PyObject__dict(self->_obj), name); -} - py_Ref py_getslot(py_Ref self, int i) { assert(self && self->is_ptr); assert(i >= 0 && i < self->_obj->slots); @@ -58,59 +56,8 @@ void py_setslot(py_Ref self, int i, py_Ref val) { PyObject__slots(self->_obj)[i] = *val; } -py_StackRef py_inspect_currentfunction() { - VM* vm = pk_current_vm; - if(vm->curr_decl_based_function) return vm->curr_decl_based_function; - py_Frame* frame = vm->top_frame; - if(!frame || frame->is_locals_special) return NULL; - return frame->p0; -} +py_Ref py_getbuiltin(py_Name name) { return py_getdict(pk_current_vm->builtins, name); } -py_GlobalRef py_inspect_currentmodule() { - py_Frame* frame = pk_current_vm->top_frame; - if(!frame) return NULL; - return frame->module; -} +py_Ref py_getglobal(py_Name name) { return py_getdict(pk_current_vm->main, name); } -py_Frame* py_inspect_currentframe() { return pk_current_vm->top_frame; } - -/* Stack References */ -py_Ref py_peek(int i) { - assert(i <= 0); - return pk_current_vm->stack.sp + i; -} - -void py_pop() { - VM* vm = pk_current_vm; - vm->stack.sp--; -} - -void py_shrink(int n) { - VM* vm = pk_current_vm; - vm->stack.sp -= n; -} - -void py_push(py_Ref src) { - VM* vm = pk_current_vm; - *vm->stack.sp++ = *src; -} - -void py_pushnil() { - VM* vm = pk_current_vm; - py_newnil(vm->stack.sp++); -} - -void py_pushnone() { - VM* vm = pk_current_vm; - py_newnone(vm->stack.sp++); -} - -void py_pushname(py_Name name) { - VM* vm = pk_current_vm; - py_newint(vm->stack.sp++, (uintptr_t)name); -} - -py_Ref py_pushtmp() { - VM* vm = pk_current_vm; - return vm->stack.sp++; -} \ No newline at end of file +void py_setglobal(py_Name name, py_Ref val) { py_setdict(pk_current_vm->main, name, val); } diff --git a/src/public/FrameOps.c b/src/public/FrameOps.c new file mode 100644 index 00000000..4fead75f --- /dev/null +++ b/src/public/FrameOps.c @@ -0,0 +1,43 @@ +#include "pocketpy/interpreter/frame.h" +#include "pocketpy/interpreter/vm.h" + +const char* py_Frame_sourceloc(py_Frame* self, int* lineno) { + SourceLocation loc = Frame__source_location(self); + *lineno = loc.lineno; + return loc.src->filename->data; +} + +void py_Frame_newglobals(py_Frame* frame, py_OutRef out) { + if(!frame) { + pk_mappingproxy__namedict(out, pk_current_vm->main); + return; + } + if(frame->globals->type == tp_module) { + pk_mappingproxy__namedict(out, frame->globals); + } else { + *out = *frame->globals; // dict + } +} + +void py_Frame_newlocals(py_Frame* frame, py_OutRef out) { + if(!frame) { + py_newdict(out); + return; + } + if(frame->is_locals_special) { + switch(frame->locals->type) { + case tp_locals: frame = frame->locals->_ptr; break; + case tp_dict: *out = *frame->locals; return; + case tp_nil: py_newdict(out); return; + default: c11__unreachable(); + } + } + FastLocals__to_dict(frame->locals, frame->co); + py_assign(out, py_retval()); +} + +py_StackRef py_Frame_function(py_Frame* self) { + if(self->is_locals_special) return NULL; + assert(self->p0->type == tp_function); + return self->p0; +} diff --git a/src/public/GlobalSetup.c b/src/public/GlobalSetup.c new file mode 100644 index 00000000..60486556 --- /dev/null +++ b/src/public/GlobalSetup.c @@ -0,0 +1,161 @@ +#include "pocketpy/objects/codeobject.h" +#include "pocketpy/pocketpy.h" + +#include "pocketpy/common/utils.h" +#include "pocketpy/common/name.h" +#include "pocketpy/interpreter/vm.h" + +_Thread_local VM* pk_current_vm; + +static bool pk_initialized; +static bool pk_finalized; + +static VM pk_default_vm; +static VM* pk_all_vm[16]; +static py_TValue _True, _False, _None, _NIL; + +void py_initialize() { + c11__rtassert(!pk_finalized); + + if(pk_initialized) { + // c11__abort("py_initialize() can only be called once!"); + return; + } + + pk_names_initialize(); + + // check endianness + int x = 1; + bool is_little_endian = *(char*)&x == 1; + if(!is_little_endian) c11__abort("is_little_endian != true"); + + static_assert(sizeof(py_TValue) == 24, "sizeof(py_TValue) != 24"); + static_assert(offsetof(py_TValue, extra) == 4, "offsetof(py_TValue, extra) != 4"); + + pk_current_vm = pk_all_vm[0] = &pk_default_vm; + + // initialize some convenient references + py_newbool(&_True, true); + py_newbool(&_False, false); + py_newnone(&_None); + py_newnil(&_NIL); + VM__ctor(&pk_default_vm); + + pk_initialized = true; +} + +void py_finalize() { + if(pk_finalized) c11__abort("py_finalize() can only be called once!"); + pk_finalized = true; + + for(int i = 1; i < 16; i++) { + VM* vm = pk_all_vm[i]; + if(vm) { + // temp fix https://github.com/pocketpy/pocketpy/issues/315 + // TODO: refactor VM__ctor and VM__dtor + pk_current_vm = vm; + VM__dtor(vm); + PK_FREE(vm); + } + } + pk_current_vm = &pk_default_vm; + VM__dtor(&pk_default_vm); + pk_current_vm = NULL; + + pk_names_finalize(); +} + +int py_currentvm() { + for(int i = 0; i < 16; i++) { + if(pk_all_vm[i] == pk_current_vm) return i; + } + return -1; +} + +void py_switchvm(int index) { + if(index < 0 || index >= 16) c11__abort("invalid vm index"); + if(!pk_all_vm[index]) { + pk_current_vm = pk_all_vm[index] = PK_MALLOC(sizeof(VM)); + memset(pk_current_vm, 0, sizeof(VM)); + VM__ctor(pk_all_vm[index]); + } else { + pk_current_vm = pk_all_vm[index]; + } +} + +void py_resetvm() { + VM* vm = pk_current_vm; + VM__dtor(vm); + memset(vm, 0, sizeof(VM)); + VM__ctor(vm); +} + +void py_resetallvm() { + for(int i = 0; i < 16; i++) { + py_switchvm(i); + py_resetvm(); + } + py_switchvm(0); +} + +void* py_getvmctx() { return pk_current_vm->ctx; } + +void py_setvmctx(void* ctx) { pk_current_vm->ctx = ctx; } + +py_Callbacks* py_callbacks() { return &pk_current_vm->callbacks; } + +///////////////////////////// + +void py_sys_setargv(int argc, char** argv) { + py_GlobalRef sys = py_getmodule("sys"); + py_Ref argv_list = py_getdict(sys, py_name("argv")); + py_list_clear(argv_list); + for(int i = 0; i < argc; i++) { + py_newstr(py_list_emplace(argv_list), argv[i]); + } +} + +void py_sys_settrace(py_TraceFunc func, bool reset) { + TraceInfo* info = &pk_current_vm->trace_info; + info->func = func; + if(!reset) return; + if(info->prev_loc.src) { + PK_DECREF(info->prev_loc.src); + info->prev_loc.src = NULL; + } + info->prev_loc.lineno = -1; +} + +int py_gc_collect() { + ManagedHeap* heap = &pk_current_vm->heap; + return ManagedHeap__collect(heap); +} + +///////////////////////////// + +void* py_malloc(size_t size) { return PK_MALLOC(size); } + +void* py_realloc(void* ptr, size_t size) { return PK_REALLOC(ptr, size); } + +void py_free(void* ptr) { PK_FREE(ptr); } + +///////////////////////////// + +py_GlobalRef py_True() { return &_True; } + +py_GlobalRef py_False() { return &_False; } + +py_GlobalRef py_None() { return &_None; } + +py_GlobalRef py_NIL() { return &_NIL; } + +///////////////////////////// + +const char* pk_opname(Opcode op) { + const static char* OP_NAMES[] = { +#define OPCODE(name) #name, +#include "pocketpy/xmacros/opcodes.h" +#undef OPCODE + }; + return OP_NAMES[op]; +} diff --git a/src/public/Inspection.c b/src/public/Inspection.c new file mode 100644 index 00000000..adab739c --- /dev/null +++ b/src/public/Inspection.c @@ -0,0 +1,28 @@ +#include "pocketpy/interpreter/frame.h" +#include "pocketpy/interpreter/vm.h" + +py_StackRef py_inspect_currentfunction() { + VM* vm = pk_current_vm; + if(vm->curr_decl_based_function) return vm->curr_decl_based_function; + py_Frame* frame = vm->top_frame; + if(!frame || frame->is_locals_special) return NULL; + return frame->p0; +} + +py_GlobalRef py_inspect_currentmodule() { + py_Frame* frame = pk_current_vm->top_frame; + if(!frame) return NULL; + return frame->module; +} + +py_Frame* py_inspect_currentframe() { return pk_current_vm->top_frame; } + +void py_newglobals(py_OutRef out) { + py_Frame* frame = pk_current_vm->top_frame; + py_Frame_newglobals(frame, out); +} + +void py_newlocals(py_OutRef out) { + py_Frame* frame = pk_current_vm->top_frame; + py_Frame_newlocals(frame, out); +} diff --git a/src/public/ModuleSystem.c b/src/public/ModuleSystem.c new file mode 100644 index 00000000..099e7785 --- /dev/null +++ b/src/public/ModuleSystem.c @@ -0,0 +1,191 @@ +#include "pocketpy/common/str.h" +#include "pocketpy/objects/base.h" +#include "pocketpy/objects/codeobject.h" +#include "pocketpy/pocketpy.h" +#include "pocketpy/common/utils.h" +#include "pocketpy/common/sstream.h" +#include "pocketpy/interpreter/vm.h" +#include "pocketpy/common/_generated.h" + + +py_Ref py_getmodule(const char* path) { + VM* vm = pk_current_vm; + return BinTree__try_get(&vm->modules, (void*)path); +} + +py_Ref py_newmodule(const char* path) { + int path_len = strlen(path); + if(path_len > PK_MAX_MODULE_PATH_LEN) c11__abort("module path too long: %s", path); + if(path_len == 0) c11__abort("module path cannot be empty"); + + py_ModuleInfo* mi = py_newobject(py_retval(), tp_module, -1, sizeof(py_ModuleInfo)); + + int last_dot = c11_sv__rindex((c11_sv){path, path_len}, '.'); + if(last_dot == -1) { + mi->name = c11_string__new(path); + mi->package = c11_string__new(""); + } else { + const char* start = path + last_dot + 1; + mi->name = c11_string__new(start); + mi->package = c11_string__new2(path, last_dot); + } + + mi->path = c11_string__new(path); + path = mi->path->data; + + // we do not allow override in order to avoid memory leak + // it is because Module objects are not garbage collected + bool exists = BinTree__contains(&pk_current_vm->modules, (void*)path); + if(exists) c11__abort("module '%s' already exists", path); + + BinTree__set(&pk_current_vm->modules, (void*)path, py_retval()); + py_GlobalRef retval = py_getmodule(path); + mi->self = retval; + + // setup __name__ + py_newstrv(py_emplacedict(retval, __name__), c11_string__sv(mi->name)); + // setup __package__ + py_newstrv(py_emplacedict(retval, __package__), c11_string__sv(mi->package)); + // setup __path__ + py_newstrv(py_emplacedict(retval, __path__), c11_string__sv(mi->path)); + return retval; +} + +static void py_ModuleInfo__dtor(py_ModuleInfo* mi) { + c11_string__delete(mi->name); + c11_string__delete(mi->package); + c11_string__delete(mi->path); +} + +py_Type pk_module__register() { + py_Type type = pk_newtype("module", tp_object, NULL, (py_Dtor)py_ModuleInfo__dtor, false, true); + return type; +} + +int load_module_from_dll_desktop_only(const char* path) PY_RAISE PY_RETURN; + +int py_import(const char* path_cstr) { + VM* vm = pk_current_vm; + c11_sv path = {path_cstr, strlen(path_cstr)}; + if(path.size == 0) return ValueError("empty module name"); + + if(path.data[0] == '.') { + // try relative import + int dot_count = 1; + while(dot_count < path.size && path.data[dot_count] == '.') + dot_count++; + + c11_sv top_filename = c11_string__sv(vm->top_frame->co->src->filename); + int is_init = c11_sv__endswith(top_filename, (c11_sv){"__init__.py", 11}); + + py_ModuleInfo* mi = py_touserdata(vm->top_frame->module); + c11_sv package_sv = c11_string__sv(mi->path); + if(package_sv.size == 0) { + return ImportError("attempted relative import with no known parent package"); + } + + c11_vector /* T=c11_sv */ cpnts = c11_sv__split(package_sv, '.'); + for(int i = is_init; i < dot_count; i++) { + if(cpnts.length == 0) + return ImportError("attempted relative import beyond top-level package"); + c11_vector__pop(&cpnts); + } + + if(dot_count < path.size) { + c11_sv last_cpnt = c11_sv__slice(path, dot_count); + c11_vector__push(c11_sv, &cpnts, last_cpnt); + } + + // join cpnts + c11_sbuf buf; + c11_sbuf__ctor(&buf); + for(int i = 0; i < cpnts.length; i++) { + if(i > 0) c11_sbuf__write_char(&buf, '.'); + c11_sbuf__write_sv(&buf, c11__getitem(c11_sv, &cpnts, i)); + } + + c11_vector__dtor(&cpnts); + c11_string* new_path = c11_sbuf__submit(&buf); + int res = py_import(new_path->data); + c11_string__delete(new_path); + return res; + } + + assert(path.data[0] != '.' && path.data[path.size - 1] != '.'); + + // check existing module + py_GlobalRef ext_mod = py_getmodule(path.data); + if(ext_mod) { + py_assign(py_retval(), ext_mod); + return true; + } + + if(vm->callbacks.lazyimport) { + py_GlobalRef lazymod = vm->callbacks.lazyimport(path_cstr); + if(lazymod) { + c11__rtassert(py_istype(lazymod, tp_module)); + py_assign(py_retval(), lazymod); + return 1; + } + } + + // try import + c11_string* slashed_path = c11_sv__replace(path, '.', PK_PLATFORM_SEP); + c11_string* filename = c11_string__new3("%s.py", slashed_path->data); + + bool need_free = true; + const char* data = load_kPythonLib(path_cstr); + if(data != NULL) { + need_free = false; + goto __SUCCESS; + } + + data = vm->callbacks.importfile(filename->data); + if(data != NULL) goto __SUCCESS; + + c11_string__delete(filename); + filename = c11_string__new3("%s%c__init__.py", slashed_path->data, PK_PLATFORM_SEP); + data = vm->callbacks.importfile(filename->data); + if(data != NULL) goto __SUCCESS; + + c11_string__delete(filename); + c11_string__delete(slashed_path); + // not found + return load_module_from_dll_desktop_only(path_cstr); + +__SUCCESS: + do { + } while(0); + py_GlobalRef mod = py_newmodule(path_cstr); + bool ok = py_exec((const char*)data, filename->data, EXEC_MODE, mod); + py_assign(py_retval(), mod); + + c11_string__delete(filename); + c11_string__delete(slashed_path); + if(need_free) PK_FREE((void*)data); + return ok ? 1 : -1; +} + +bool py_importlib_reload(py_Ref module) { + VM* vm = pk_current_vm; + py_ModuleInfo* mi = py_touserdata(module); + // We should ensure that the module is its original py_GlobalRef + module = mi->self; + c11_sv path = c11_string__sv(mi->path); + c11_string* slashed_path = c11_sv__replace(path, '.', PK_PLATFORM_SEP); + c11_string* filename = c11_string__new3("%s.py", slashed_path->data); + char* data = vm->callbacks.importfile(filename->data); + if(data == NULL) { + c11_string__delete(filename); + filename = c11_string__new3("%s%c__init__.py", slashed_path->data, PK_PLATFORM_SEP); + data = vm->callbacks.importfile(filename->data); + } + c11_string__delete(slashed_path); + if(data == NULL) return ImportError("module '%v' not found", path); + // py_cleardict(module); BUG: removing old classes will cause RELOAD_MODE to fail + bool ok = py_exec(data, filename->data, RELOAD_MODE, module); + c11_string__delete(filename); + PK_FREE(data); + py_assign(py_retval(), module); + return ok; +} diff --git a/src/public/py_dict.c b/src/public/PyDict.c similarity index 100% rename from src/public/py_dict.c rename to src/public/PyDict.c diff --git a/src/public/py_exception.c b/src/public/PyException.c similarity index 98% rename from src/public/py_exception.c rename to src/public/PyException.c index cd4267ba..18c976cc 100644 --- a/src/public/py_exception.c +++ b/src/public/PyException.c @@ -141,28 +141,6 @@ py_Type pk_StopIteration__register() { return type; } -////////////////////////////////////////////////// -bool py_checkexc() { - VM* vm = pk_current_vm; - return !py_isnil(&vm->unhandled_exc); -} - -bool py_matchexc(py_Type type) { - VM* vm = pk_current_vm; - if(py_isnil(&vm->unhandled_exc)) return false; - bool ok = py_issubclass(vm->unhandled_exc.type, type); - if(ok) vm->last_retval = vm->unhandled_exc; - return ok; -} - -void py_clearexc(py_StackRef p0) { - VM* vm = pk_current_vm; - py_newnil(&vm->unhandled_exc); - if(p0) { - c11__rtassert(p0 >= vm->stack.begin && p0 <= vm->stack.sp); - vm->stack.sp = p0; - } -} static void c11_sbuf__write_exc(c11_sbuf* self, py_Ref exc) { c11_sbuf__write_cstr(self, "Traceback (most recent call last):\n"); @@ -212,6 +190,29 @@ char* safe_stringify_exception(py_Ref exc) { return c11_strdup(message); } +////////////////////////////////////////////////// +bool py_checkexc() { + VM* vm = pk_current_vm; + return !py_isnil(&vm->unhandled_exc); +} + +bool py_matchexc(py_Type type) { + VM* vm = pk_current_vm; + if(py_isnil(&vm->unhandled_exc)) return false; + bool ok = py_issubclass(vm->unhandled_exc.type, type); + if(ok) vm->last_retval = vm->unhandled_exc; + return ok; +} + +void py_clearexc(py_StackRef p0) { + VM* vm = pk_current_vm; + py_newnil(&vm->unhandled_exc); + if(p0) { + c11__rtassert(p0 >= vm->stack.begin && p0 <= vm->stack.sp); + vm->stack.sp = p0; + } +} + void py_printexc() { char* msg = py_formatexc(); if(!msg) return; @@ -299,4 +300,10 @@ bool KeyError(py_Ref key) { bool ok = py_tpcall(tp_KeyError, 1, key); if(!ok) return false; return py_raise(py_retval()); -} \ No newline at end of file +} + +bool StopIteration() { + bool ok = py_tpcall(tp_StopIteration, 0, NULL); + if(!ok) return false; + return py_raise(py_retval()); +} diff --git a/src/public/py_list.c b/src/public/PyList.c similarity index 100% rename from src/public/py_list.c rename to src/public/PyList.c diff --git a/src/public/py_slice.c b/src/public/PySlice.c similarity index 91% rename from src/public/py_slice.c rename to src/public/PySlice.c index 5d0fe627..3e69ab1d 100644 --- a/src/public/py_slice.c +++ b/src/public/PySlice.c @@ -4,12 +4,20 @@ #include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" -void py_newslice(py_OutRef out) { +py_ObjectRef py_newslice(py_OutRef out) { VM* vm = pk_current_vm; PyObject* obj = ManagedHeap__gcnew(&vm->heap, tp_slice, 3, 0); out->type = tp_slice; out->is_ptr = true; out->_obj = obj; + return PyObject__slots(obj); +} + +void py_newsliceint(py_OutRef out, py_i64 start, py_i64 stop, py_i64 step) { + py_Ref slots = py_newslice(out); + py_newint(&slots[0], start); + py_newint(&slots[1], stop); + py_newint(&slots[2], step); } static bool slice__new__(int argc, py_Ref argv) { diff --git a/src/public/py_tuple.c b/src/public/PyTuple.c similarity index 100% rename from src/public/py_tuple.c rename to src/public/PyTuple.c diff --git a/src/public/py_ops.c b/src/public/PythonOps.c similarity index 78% rename from src/public/py_ops.c rename to src/public/PythonOps.c index 16e63a83..3767b278 100644 --- a/src/public/py_ops.c +++ b/src/public/PythonOps.c @@ -4,6 +4,48 @@ #include "pocketpy/objects/base.h" #include "pocketpy/pocketpy.h" +bool py_binaryadd(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __add__, __radd__); } + +bool py_binarysub(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __sub__, __rsub__); } + +bool py_binarymul(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __mul__, __rmul__); } + +bool py_binarytruediv(py_Ref lhs, py_Ref rhs) { + return py_binaryop(lhs, rhs, __truediv__, __rtruediv__); +} + +bool py_binaryfloordiv(py_Ref lhs, py_Ref rhs) { + return py_binaryop(lhs, rhs, __floordiv__, __rfloordiv__); +} + +bool py_binarymod(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __mod__, __rmod__); } + +bool py_binarypow(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __pow__, __rpow__); } + +bool py_binarylshift(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __lshift__, 0); } + +bool py_binaryrshift(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __rshift__, 0); } + +bool py_binaryand(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __and__, 0); } + +bool py_binaryor(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __or__, 0); } + +bool py_binaryxor(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __xor__, 0); } + +bool py_binarymatmul(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __matmul__, 0); } + +bool py_eq(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __eq__, __eq__); } + +bool py_ne(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __ne__, __ne__); } + +bool py_lt(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __lt__, __gt__); } + +bool py_le(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __le__, __ge__); } + +bool py_gt(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __gt__, __lt__); } + +bool py_ge(py_Ref lhs, py_Ref rhs) { return py_binaryop(lhs, rhs, __ge__, __le__); } + bool py_isidentical(py_Ref lhs, py_Ref rhs) { if(lhs->type != rhs->type) return false; switch(lhs->type) { @@ -52,6 +94,29 @@ int py_bool(py_Ref val) { } } +int py_equal(py_Ref lhs, py_Ref rhs) { + if(py_isidentical(lhs, rhs)) return 1; + if(!py_eq(lhs, rhs)) return -1; + return py_bool(py_retval()); +} + +int py_less(py_Ref lhs, py_Ref rhs) { + if(!py_lt(lhs, rhs)) return -1; + return py_bool(py_retval()); +} + +bool py_callable(py_Ref val) { + switch(val->type) { + case tp_nativefunc: return true; + case tp_function: return true; + case tp_type: return true; + case tp_boundmethod: return true; + case tp_staticmethod: return true; + case tp_classmethod: return true; + default: return py_tpfindmagic(val->type, __call__); + } +} + bool py_hash(py_Ref val, int64_t* out) { py_TypeInfo* ti = pk_typeinfo(val->type); do { @@ -119,6 +184,20 @@ int py_next(py_Ref val) { return -1; } +bool py_str(py_Ref val) { + if(val->type == tp_str) { + py_assign(py_retval(), val); + return true; + } + py_Ref tmp = py_tpfindmagic(val->type, __str__); + if(!tmp) return py_repr(val); + return py_call(tmp, 1, val); +} + +bool py_repr(py_Ref val) { return pk_callmagic(__repr__, 1, val); } + +bool py_len(py_Ref val) { return pk_callmagic(__len__, 1, val); } + bool py_getattr(py_Ref self, py_Name name) { // https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance py_TypeInfo* ti = pk_typeinfo(self->type); @@ -277,14 +356,3 @@ bool py_delitem(py_Ref self, py_Ref key) { py_shrink(2); return ok; } - -int py_equal(py_Ref lhs, py_Ref rhs) { - if(py_isidentical(lhs, rhs)) return 1; - if(!py_eq(lhs, rhs)) return -1; - return py_bool(py_retval()); -} - -int py_less(py_Ref lhs, py_Ref rhs) { - if(!py_lt(lhs, rhs)) return -1; - return py_bool(py_retval()); -} \ No newline at end of file diff --git a/src/public/internal.c b/src/public/StackOps.c similarity index 54% rename from src/public/internal.c rename to src/public/StackOps.c index 964800d7..4cdd1ee7 100644 --- a/src/public/internal.c +++ b/src/public/StackOps.c @@ -1,140 +1,61 @@ -#include "pocketpy/interpreter/typeinfo.h" -#include "pocketpy/objects/codeobject.h" #include "pocketpy/pocketpy.h" - -#include "pocketpy/common/utils.h" -#include "pocketpy/common/name.h" #include "pocketpy/interpreter/vm.h" -_Thread_local VM* pk_current_vm; - -static bool pk_initialized; -static bool pk_finalized; - -static VM pk_default_vm; -static VM* pk_all_vm[16]; -static py_TValue _True, _False, _None, _NIL; - -void py_initialize() { - c11__rtassert(!pk_finalized); - - if(pk_initialized) { - // c11__abort("py_initialize() can only be called once!"); - return; - } - - pk_names_initialize(); - - // check endianness - int x = 1; - bool is_little_endian = *(char*)&x == 1; - if(!is_little_endian) c11__abort("is_little_endian != true"); - - static_assert(sizeof(py_TValue) == 24, "sizeof(py_TValue) != 24"); - static_assert(offsetof(py_TValue, extra) == 4, "offsetof(py_TValue, extra) != 4"); - - pk_current_vm = pk_all_vm[0] = &pk_default_vm; - - // initialize some convenient references - py_newbool(&_True, true); - py_newbool(&_False, false); - py_newnone(&_None); - py_newnil(&_NIL); - VM__ctor(&pk_default_vm); - - pk_initialized = true; +PK_INLINE py_Ref py_peek(int i) { + assert(i <= 0); + return pk_current_vm->stack.sp + i; } -void* py_malloc(size_t size) { return PK_MALLOC(size); } - -void* py_realloc(void* ptr, size_t size) { return PK_REALLOC(ptr, size); } - -void py_free(void* ptr) { PK_FREE(ptr); } - -py_GlobalRef py_True() { return &_True; } - -py_GlobalRef py_False() { return &_False; } - -py_GlobalRef py_None() { return &_None; } - -py_GlobalRef py_NIL() { return &_NIL; } - -void py_finalize() { - if(pk_finalized) c11__abort("py_finalize() can only be called once!"); - pk_finalized = true; - - for(int i = 1; i < 16; i++) { - VM* vm = pk_all_vm[i]; - if(vm) { - // temp fix https://github.com/pocketpy/pocketpy/issues/315 - // TODO: refactor VM__ctor and VM__dtor - pk_current_vm = vm; - VM__dtor(vm); - PK_FREE(vm); - } - } - pk_current_vm = &pk_default_vm; - VM__dtor(&pk_default_vm); - pk_current_vm = NULL; - - pk_names_finalize(); -} - -void py_switchvm(int index) { - if(index < 0 || index >= 16) c11__abort("invalid vm index"); - if(!pk_all_vm[index]) { - pk_current_vm = pk_all_vm[index] = PK_MALLOC(sizeof(VM)); - memset(pk_current_vm, 0, sizeof(VM)); - VM__ctor(pk_all_vm[index]); - } else { - pk_current_vm = pk_all_vm[index]; - } -} - -void py_resetvm() { +PK_INLINE void py_push(py_Ref src) { VM* vm = pk_current_vm; - VM__dtor(vm); - memset(vm, 0, sizeof(VM)); - VM__ctor(vm); + *vm->stack.sp++ = *src; } -void py_resetallvm() { - for(int i = 0; i < 16; i++) { - py_switchvm(i); - py_resetvm(); - } - py_switchvm(0); +PK_INLINE void py_pushnil() { + VM* vm = pk_current_vm; + py_newnil(vm->stack.sp++); } -int py_currentvm() { - for(int i = 0; i < 16; i++) { - if(pk_all_vm[i] == pk_current_vm) return i; - } - return -1; +PK_INLINE void py_pushnone() { + VM* vm = pk_current_vm; + py_newnone(vm->stack.sp++); } -void* py_getvmctx() { return pk_current_vm->ctx; } - -void py_setvmctx(void* ctx) { pk_current_vm->ctx = ctx; } - -void py_sys_setargv(int argc, char** argv) { - py_GlobalRef sys = py_getmodule("sys"); - py_Ref argv_list = py_getdict(sys, py_name("argv")); - py_list_clear(argv_list); - for(int i = 0; i < argc; i++) { - py_newstr(py_list_emplace(argv_list), argv[i]); - } +PK_INLINE void py_pushname(py_Name name) { + VM* vm = pk_current_vm; + py_newint(vm->stack.sp++, (uintptr_t)name); } -py_Callbacks* py_callbacks() { return &pk_current_vm->callbacks; } +PK_INLINE void py_pop() { + VM* vm = pk_current_vm; + vm->stack.sp--; +} -const char* pk_opname(Opcode op) { - const static char* OP_NAMES[] = { -#define OPCODE(name) #name, -#include "pocketpy/xmacros/opcodes.h" -#undef OPCODE - }; - return OP_NAMES[op]; +PK_INLINE void py_shrink(int n) { + VM* vm = pk_current_vm; + vm->stack.sp -= n; +} + +PK_INLINE py_Ref py_pushtmp() { + VM* vm = pk_current_vm; + return vm->stack.sp++; +} + +PK_INLINE bool py_pushmethod(py_Name name) { + bool ok = pk_loadmethod(py_peek(-1), name); + if(ok) pk_current_vm->stack.sp++; + return ok; +} + +bool py_pusheval(const char* expr, py_GlobalRef module) { + bool ok = py_exec(expr, "", EVAL_MODE, module); + if(!ok) return false; + py_push(py_retval()); + return true; +} + +PK_INLINE bool py_vectorcall(uint16_t argc, uint16_t kwargc) { + return VM__vectorcall(pk_current_vm, argc, kwargc, false) != RES_ERROR; } bool py_call(py_Ref f, int argc, py_Ref argv) { @@ -180,15 +101,15 @@ bool py_callcfunc(py_CFunction f, int argc, py_Ref argv) { } #endif -bool py_vectorcall(uint16_t argc, uint16_t kwargc) { - return VM__vectorcall(pk_current_vm, argc, kwargc, false) != RES_ERROR; +bool py_tpcall(py_Type type, int argc, py_Ref argv) { + return py_call(py_tpobject(type), argc, argv); } -PK_INLINE py_Ref py_retval() { return &pk_current_vm->last_retval; } - -bool py_pushmethod(py_Name name) { - bool ok = pk_loadmethod(py_peek(-1), name); - if(ok) pk_current_vm->stack.sp++; +bool py_binaryop(py_Ref lhs, py_Ref rhs, py_Name op, py_Name rop) { + py_push(lhs); + py_push(rhs); + bool ok = pk_stack_binaryop(pk_current_vm, op, rop); + py_shrink(2); return ok; } @@ -267,10 +188,6 @@ bool pk_loadmethod(py_StackRef self, py_Name name) { return false; } -bool py_tpcall(py_Type type, int argc, py_Ref argv) { - return py_call(py_tpobject(type), argc, argv); -} - bool pk_callmagic(py_Name name, int argc, py_Ref argv) { assert(argc >= 1); // assert(py_ismagicname(name)); @@ -278,9 +195,3 @@ bool pk_callmagic(py_Name name, int argc, py_Ref argv) { if(!tmp) return AttributeError(argv, name); return py_call(tmp, argc, argv); } - -bool StopIteration() { - bool ok = py_tpcall(tp_StopIteration, 0, NULL); - if(!ok) return false; - return py_raise(py_retval()); -} diff --git a/src/public/TypeSystem.c b/src/public/TypeSystem.c new file mode 100644 index 00000000..fde04a5b --- /dev/null +++ b/src/public/TypeSystem.c @@ -0,0 +1,104 @@ +#include "pocketpy/objects/base.h" +#include "pocketpy/pocketpy.h" + +#include "pocketpy/interpreter/vm.h" + +py_Type py_newtype(const char* name, py_Type base, const py_GlobalRef module, void (*dtor)(void*)) { + if(strlen(name) == 0) c11__abort("type name cannot be empty"); + py_Type type = pk_newtype(name, base, module, dtor, false, false); + if(module) py_setdict(module, py_name(name), py_tpobject(type)); + return type; +} + +PK_INLINE bool py_istype(py_Ref self, py_Type type) { return self->type == type; } + +PK_INLINE py_Type py_typeof(py_Ref self) { return 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) { + assert(derived != 0 && base != 0); + py_TypeInfo* derived_ti = pk_typeinfo(derived); + py_TypeInfo* base_ti = pk_typeinfo(base); + do { + if(derived_ti == base_ti) return true; + derived_ti = derived_ti->base_ti; + } while(derived_ti); + return false; +} + +py_Type py_gettype(const char* module, py_Name name) { + py_Ref mod; + if(module != NULL) { + mod = py_getmodule(module); + if(!mod) return tp_nil; + } else { + mod = pk_current_vm->builtins; + } + py_Ref object = py_getdict(mod, name); + if(object && py_istype(object, tp_type)) return py_totype(object); + return tp_nil; +} + +bool py_checktype(py_Ref self, py_Type type) { + if(self->type == type) return true; + return TypeError("expected '%t', got '%t'", type, self->type); +} + +bool py_checkinstance(py_Ref self, py_Type type) { + if(py_isinstance(self, type)) return true; + return TypeError("expected '%t' or its subclass, got '%t'", type, self->type); +} + +PK_DEPRECATED py_Ref py_tpgetmagic(py_Type type, py_Name name) { + // assert(py_ismagicname(name)); + py_TypeInfo* ti = pk_typeinfo(type); + py_Ref retval = py_getdict(&ti->self, name); + return retval != NULL ? retval : py_NIL(); +} + +PK_INLINE py_Ref py_tpfindmagic(py_Type t, py_Name name) { + // assert(py_ismagicname(name)); + return py_tpfindname(t, name); +} + +PK_INLINE py_ItemRef py_tpfindname(py_Type type, py_Name name) { + py_TypeInfo* ti = pk_typeinfo(type); + return pk_tpfindname(ti, name); +} + +PK_INLINE py_Type py_tpbase(py_Type t) { + assert(t); + py_TypeInfo* ti = pk_typeinfo(t); + return ti->base; +} + +py_Ref py_tpobject(py_Type type) { + assert(type); + return &pk_typeinfo(type)->self; +} + +const char* py_tpname(py_Type type) { + if(!type) return "nil"; + py_Name name = pk_typeinfo(type)->name; + return py_name2str(name); +} + +void py_tpsetfinal(py_Type type) { + assert(type); + py_TypeInfo* ti = pk_typeinfo(type); + ti->is_final = true; +} + +void py_tphookattributes(py_Type type, + bool (*getattribute)(py_Ref self, py_Name name), + bool (*setattribute)(py_Ref self, py_Name name, py_Ref val), + bool (*delattribute)(py_Ref self, py_Name name), + bool (*getunboundmethod)(py_Ref self, py_Name name)) { + assert(type); + py_TypeInfo* ti = pk_typeinfo(type); + ti->getattribute = getattribute; + ti->setattribute = setattribute; + ti->delattribute = delattribute; + ti->getunboundmethod = getunboundmethod; +} diff --git a/src/public/cast.c b/src/public/ValueCast.c similarity index 65% rename from src/public/cast.c rename to src/public/ValueCast.c index 39d8fb66..f2f2cff3 100644 --- a/src/public/cast.c +++ b/src/public/ValueCast.c @@ -4,7 +4,7 @@ #include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" -int64_t py_toint(py_Ref self) { +py_i64 py_toint(py_Ref self) { assert(self->type == tp_int); return self->_i64; } @@ -55,3 +55,30 @@ void* py_touserdata(py_Ref self) { assert(self && self->is_ptr); return PyObject__userdata(self->_obj); } + +const char* py_tostr(py_Ref self) { return pk_tostr(self)->data; } + +const char* py_tostrn(py_Ref self, int* size) { + c11_string* ud = pk_tostr(self); + *size = ud->size; + return ud->data; +} + +c11_sv py_tosv(py_Ref self) { + c11_string* ud = pk_tostr(self); + return c11_string__sv(ud); +} + +unsigned char* py_tobytes(py_Ref self, int* size) { + assert(self->type == tp_bytes); + c11_bytes* ud = PyObject__userdata(self->_obj); + *size = ud->size; + return ud->data; +} + +void py_bytes_resize(py_Ref self, int size) { + assert(self->type == tp_bytes); + c11_bytes* ud = PyObject__userdata(self->_obj); + if(size > ud->size) c11__abort("bytes can only be resized down: %d > %d", ud->size, size); + ud->size = size; +} diff --git a/src/public/values.c b/src/public/ValueCreation.c similarity index 62% rename from src/public/values.c rename to src/public/ValueCreation.c index 0c35edb0..426cc8e7 100644 --- a/src/public/values.c +++ b/src/public/ValueCreation.c @@ -32,6 +32,54 @@ void py_newbool(py_OutRef out, bool val) { out->_bool = val; } +void py_newstr(py_OutRef out, const char* data) { py_newstrv(out, (c11_sv){data, strlen(data)}); } + +char* py_newstrn(py_OutRef out, int size) { + if(size < 16) { + out->type = tp_str; + out->is_ptr = false; + c11_string* ud = (c11_string*)(&out->extra); + c11_string__ctor3(ud, size); + return ud->data; + } + ManagedHeap* heap = &pk_current_vm->heap; + int total_size = sizeof(c11_string) + size + 1; + PyObject* obj = ManagedHeap__gcnew(heap, tp_str, 0, total_size); + c11_string* ud = PyObject__userdata(obj); + c11_string__ctor3(ud, size); + out->type = tp_str; + out->is_ptr = true; + out->_obj = obj; + return ud->data; +} + +void py_newstrv(py_OutRef out, c11_sv sv) { + char* data = py_newstrn(out, sv.size); + memcpy(data, sv.data, sv.size); +} + +void py_newfstr(py_OutRef out, const char* fmt, ...) { + c11_sbuf buf; + c11_sbuf__ctor(&buf); + va_list args; + va_start(args, fmt); + pk_vsprintf(&buf, fmt, args); + va_end(args); + c11_sbuf__py_submit(&buf, out); +} + +unsigned char* py_newbytes(py_OutRef out, int size) { + ManagedHeap* heap = &pk_current_vm->heap; + // 4 bytes size + data + PyObject* obj = ManagedHeap__gcnew(heap, tp_bytes, 0, sizeof(c11_bytes) + size); + c11_bytes* ud = PyObject__userdata(obj); + ud->size = size; + out->type = tp_bytes; + out->is_ptr = true; + out->_obj = obj; + return ud->data; +} + void py_newnone(py_OutRef out) { out->type = tp_NoneType; out->is_ptr = false; @@ -58,66 +106,6 @@ void py_newnativefunc(py_OutRef out, py_CFunction f) { out->_cfunc = f; } -void py_bindmethod(py_Type type, const char* name, py_CFunction f) { - py_TValue tmp; - py_newnativefunc(&tmp, f); - py_setdict(py_tpobject(type), py_name(name), &tmp); -} - -void py_bindstaticmethod(py_Type type, const char* name, py_CFunction f) { - py_TValue tmp; - py_newnativefunc(&tmp, f); - bool ok = py_tpcall(tp_staticmethod, 1, &tmp); - if(!ok) { - py_printexc(); - c11__abort("py_bindstaticmethod(): failed to create staticmethod"); - } - py_setdict(py_tpobject(type), py_name(name), py_retval()); -} - -void py_bindfunc(py_Ref obj, const char* name, py_CFunction f) { - py_TValue tmp; - py_newnativefunc(&tmp, f); - py_setdict(obj, py_name(name), &tmp); -} - -void py_bindproperty(py_Type type, const char* name, py_CFunction getter, py_CFunction setter) { - py_TValue tmp; - py_newobject(&tmp, tp_property, 2, 0); - py_newnativefunc(py_getslot(&tmp, 0), getter); - if(setter) { - py_newnativefunc(py_getslot(&tmp, 1), setter); - } else { - py_setslot(&tmp, 1, py_None()); - } - py_setdict(py_tpobject(type), py_name(name), &tmp); -} - -void py_bindmagic(py_Type type, py_Name name, py_CFunction f) { - py_Ref tmp = py_emplacedict(py_tpobject(type), name); - py_newnativefunc(tmp, f); -} - -void py_bind(py_Ref obj, const char* sig, py_CFunction f) { - py_Ref tmp = py_pushtmp(); - py_Name name = py_newfunction(tmp, sig, f, NULL, 0); - py_setdict(obj, name, tmp); - py_pop(); -} - -void py_macrobind(const char* sig, py_CFunction f) { - py_Ref tmp = py_pushtmp(); - py_Name name = py_newfunction(tmp, sig, f, NULL, 0); - NameDict__set(&pk_current_vm->compile_time_funcs, name, tmp); - py_pop(); -} - -py_ItemRef py_macroget(py_Name name) { - NameDict* d = &pk_current_vm->compile_time_funcs; - if(d->length == 0) return NULL; - return NameDict__try_get(d, name); -} - py_Name py_newfunction(py_OutRef out, const char* sig, py_CFunction f, diff --git a/src/public/typecast.c b/src/public/typecast.c deleted file mode 100644 index 69dbf690..00000000 --- a/src/public/typecast.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "pocketpy/objects/base.h" -#include "pocketpy/pocketpy.h" - -#include "pocketpy/objects/object.h" -#include "pocketpy/interpreter/vm.h" - -PK_INLINE bool py_istype(py_Ref self, py_Type type) { return self->type == type; } - -bool py_checktype(py_Ref self, py_Type type) { - if(self->type == type) return true; - return TypeError("expected '%t', got '%t'", type, self->type); -} - -bool py_checkinstance(py_Ref self, py_Type type) { - if(py_isinstance(self, type)) return true; - return TypeError("expected '%t' or its subclass, 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) { - assert(derived != 0 && base != 0); - py_TypeInfo* derived_ti = pk_typeinfo(derived); - py_TypeInfo* base_ti = pk_typeinfo(base); - do { - if(derived_ti == base_ti) return true; - derived_ti = derived_ti->base_ti; - } while(derived_ti); - return false; -} - -py_Type py_typeof(py_Ref self) { return self->type; } - -py_Type py_gettype(const char* module, py_Name name) { - py_Ref mod; - if(module != NULL) { - mod = py_getmodule(module); - if(!mod) return tp_nil; - } else { - mod = pk_current_vm->builtins; - } - py_Ref object = py_getdict(mod, name); - if(object && py_istype(object, tp_type)) return py_totype(object); - return tp_nil; -} \ No newline at end of file From f12a379760f80dc396f1b0fa30a9fddaff66e48b Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 8 Sep 2025 20:07:54 +0800 Subject: [PATCH 5/7] add maxlen for deque --- python/collections.py | 42 ++++++++++++++++++++++++++++++----------- src/common/_generated.c | 2 +- tests/72_collections.py | 28 +++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/python/collections.py b/python/collections.py index 8739d837..34df4c65 100644 --- a/python/collections.py +++ b/python/collections.py @@ -27,20 +27,29 @@ class defaultdict(dict): class deque[T]: - _data: list[T] _head: int _tail: int + _maxlen: int | None _capacity: int + _data: list[T] + + def __init__(self, iterable: Iterable[T] = None, maxlen: int | None = None): + if maxlen is not None: + assert maxlen > 0 - def __init__(self, iterable: Iterable[T] = None): - self._data = [None] * 8 # type: ignore self._head = 0 self._tail = 0 - self._capacity = len(self._data) + self._maxlen = maxlen + self._capacity = 8 if maxlen is None else maxlen + 1 + self._data = [None] * self._capacity # type: ignore if iterable is not None: self.extend(iterable) + @property + def maxlen(self) -> int | None: + return self._maxlen + def __resize_2x(self): backup = list(self) self._capacity *= 2 @@ -51,19 +60,25 @@ class deque[T]: self._data.extend([None] * (self._capacity - len(backup))) def append(self, x: T): + if (self._tail + 1) % self._capacity == self._head: + if self._maxlen is None: + self.__resize_2x() + else: + self.popleft() self._data[self._tail] = x self._tail = (self._tail + 1) % self._capacity - if (self._tail + 1) % self._capacity == self._head: - self.__resize_2x() def appendleft(self, x: T): + if (self._tail + 1) % self._capacity == self._head: + if self._maxlen is None: + self.__resize_2x() + else: + self.pop() self._head = (self._head - 1) % self._capacity self._data[self._head] = x - if (self._tail + 1) % self._capacity == self._head: - self.__resize_2x() def copy(self): - return deque(self) + return deque(self, maxlen=self.maxlen) def count(self, x: T) -> int: n = 0 @@ -84,12 +99,15 @@ class deque[T]: if self._head == self._tail: raise IndexError("pop from an empty deque") self._tail = (self._tail - 1) % self._capacity - return self._data[self._tail] + x = self._data[self._tail] + self._data[self._tail] = None + return x def popleft(self) -> T: if self._head == self._tail: raise IndexError("pop from an empty deque") x = self._data[self._head] + self._data[self._head] = None self._head = (self._head + 1) % self._capacity return x @@ -144,5 +162,7 @@ class deque[T]: return not self == other def __repr__(self) -> str: - return f"deque({list(self)!r})" + if self.maxlen is None: + return f"deque({list(self)!r})" + return f"deque({list(self)!r}, maxlen={self.maxlen})" diff --git a/src/common/_generated.c b/src/common/_generated.c index fb970f52..75316f7b 100644 --- a/src/common/_generated.c +++ b/src/common/_generated.c @@ -4,7 +4,7 @@ const char kPythonLibs_bisect[] = "\"\"\"Bisection algorithms.\"\"\"\n\ndef insort_right(a, x, lo=0, hi=None):\n \"\"\"Insert item x in list a, and keep it sorted assuming a is sorted.\n\n If x is already in a, insert it to the right of the rightmost x.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n lo = bisect_right(a, x, lo, hi)\n a.insert(lo, x)\n\ndef bisect_right(a, x, lo=0, hi=None):\n \"\"\"Return the index where to insert item x in list a, assuming a is sorted.\n\n The return value i is such that all e in a[:i] have e <= x, and all e in\n a[i:] have e > x. So if x already appears in the list, a.insert(x) will\n insert just after the rightmost x already there.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n if lo < 0:\n raise ValueError('lo must be non-negative')\n if hi is None:\n hi = len(a)\n while lo < hi:\n mid = (lo+hi)//2\n if x < a[mid]: hi = mid\n else: lo = mid+1\n return lo\n\ndef insort_left(a, x, lo=0, hi=None):\n \"\"\"Insert item x in list a, and keep it sorted assuming a is sorted.\n\n If x is already in a, insert it to the left of the leftmost x.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n lo = bisect_left(a, x, lo, hi)\n a.insert(lo, x)\n\n\ndef bisect_left(a, x, lo=0, hi=None):\n \"\"\"Return the index where to insert item x in list a, assuming a is sorted.\n\n The return value i is such that all e in a[:i] have e < x, and all e in\n a[i:] have e >= x. So if x already appears in the list, a.insert(x) will\n insert just before the leftmost x already there.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n if lo < 0:\n raise ValueError('lo must be non-negative')\n if hi is None:\n hi = len(a)\n while lo < hi:\n mid = (lo+hi)//2\n if a[mid] < x: lo = mid+1\n else: hi = mid\n return lo\n\n# Create aliases\nbisect = bisect_right\ninsort = insort_right\n"; const char kPythonLibs_builtins[] = "def all(iterable):\n for i in iterable:\n if not i:\n return False\n return True\n\ndef any(iterable):\n for i in iterable:\n if i:\n return True\n return False\n\ndef enumerate(iterable, start=0):\n n = start\n for elem in iterable:\n yield n, elem\n n += 1\n\ndef __minmax_reduce(op, args):\n if len(args) == 2: # min(1, 2)\n return args[0] if op(args[0], args[1]) else args[1]\n if len(args) == 0: # min()\n raise TypeError('expected 1 arguments, got 0')\n if len(args) == 1: # min([1, 2, 3, 4]) -> min(1, 2, 3, 4)\n args = args[0]\n args = iter(args)\n try:\n res = next(args)\n except StopIteration:\n raise ValueError('args is an empty sequence')\n while True:\n try:\n i = next(args)\n except StopIteration:\n break\n if op(i, res):\n res = i\n return res\n\ndef min(*args, key=None):\n key = key or (lambda x: x)\n return __minmax_reduce(lambda x,y: key(x)key(y), args)\n\ndef sum(iterable):\n res = 0\n for i in iterable:\n res += i\n return res\n\ndef map(f, iterable):\n for i in iterable:\n yield f(i)\n\ndef filter(f, iterable):\n for i in iterable:\n if f(i):\n yield i\n\ndef zip(a, b):\n a = iter(a)\n b = iter(b)\n while True:\n try:\n ai = next(a)\n bi = next(b)\n except StopIteration:\n break\n yield ai, bi\n\ndef reversed(iterable):\n a = list(iterable)\n a.reverse()\n return a\n\ndef sorted(iterable, key=None, reverse=False):\n a = list(iterable)\n a.sort(key=key, reverse=reverse)\n return a\n\n##### str #####\ndef __format_string(self: str, *args, **kwargs) -> str:\n def tokenizeString(s: str):\n tokens = []\n L, R = 0,0\n \n mode = None\n curArg = 0\n # lookingForKword = False\n \n while(R list[str]:\n tp_module = type(__import__('math'))\n if isinstance(obj, tp_module):\n return [k for k, _ in obj.__dict__.items()]\n names = set()\n if not isinstance(obj, type):\n obj_d = obj.__dict__\n if obj_d is not None:\n names.update([k for k, _ in obj_d.items()])\n cls = type(obj)\n else:\n cls = obj\n while cls is not None:\n names.update([k for k, _ in cls.__dict__.items()])\n cls = cls.__base__\n return sorted(list(names))\n\nclass set:\n def __init__(self, iterable=None):\n iterable = iterable or []\n self._a = {}\n self.update(iterable)\n\n def add(self, elem):\n self._a[elem] = None\n \n def discard(self, elem):\n self._a.pop(elem, None)\n\n def remove(self, elem):\n del self._a[elem]\n \n def clear(self):\n self._a.clear()\n\n def update(self, other):\n for elem in other:\n self.add(elem)\n\n def __len__(self):\n return len(self._a)\n \n def copy(self):\n return set(self._a.keys())\n \n def __and__(self, other):\n return {elem for elem in self if elem in other}\n\n def __sub__(self, other):\n return {elem for elem in self if elem not in other}\n \n def __or__(self, other):\n ret = self.copy()\n ret.update(other)\n return ret\n\n def __xor__(self, other): \n _0 = self - other\n _1 = other - self\n return _0 | _1\n\n def union(self, other):\n return self | other\n\n def intersection(self, other):\n return self & other\n\n def difference(self, other):\n return self - other\n\n def symmetric_difference(self, other): \n return self ^ other\n \n def __eq__(self, other):\n if not isinstance(other, set):\n return NotImplemented\n return len(self ^ other) == 0\n \n def __ne__(self, other):\n if not isinstance(other, set):\n return NotImplemented\n return len(self ^ other) != 0\n\n def isdisjoint(self, other):\n return len(self & other) == 0\n \n def issubset(self, other):\n return len(self - other) == 0\n \n def issuperset(self, other):\n return len(other - self) == 0\n\n def __contains__(self, elem):\n return elem in self._a\n \n def __repr__(self):\n if len(self) == 0:\n return 'set()'\n return '{'+ ', '.join([repr(i) for i in self._a.keys()]) + '}'\n \n def __iter__(self):\n return iter(self._a.keys())"; const char kPythonLibs_cmath[] = "import math\n\nclass complex:\n def __init__(self, real, imag=0):\n self._real = float(real)\n self._imag = float(imag)\n\n @property\n def real(self):\n return self._real\n \n @property\n def imag(self):\n return self._imag\n\n def conjugate(self):\n return complex(self.real, -self.imag)\n \n def __repr__(self):\n s = ['(', str(self.real)]\n s.append('-' if self.imag < 0 else '+')\n s.append(str(abs(self.imag)))\n s.append('j)')\n return ''.join(s)\n \n def __eq__(self, other):\n if type(other) is complex:\n return self.real == other.real and self.imag == other.imag\n if type(other) in (int, float):\n return self.real == other and self.imag == 0\n return NotImplemented\n \n def __ne__(self, other):\n res = self == other\n if res is NotImplemented:\n return res\n return not res\n \n def __add__(self, other):\n if type(other) is complex:\n return complex(self.real + other.real, self.imag + other.imag)\n if type(other) in (int, float):\n return complex(self.real + other, self.imag)\n return NotImplemented\n \n def __radd__(self, other):\n return self.__add__(other)\n \n def __sub__(self, other):\n if type(other) is complex:\n return complex(self.real - other.real, self.imag - other.imag)\n if type(other) in (int, float):\n return complex(self.real - other, self.imag)\n return NotImplemented\n \n def __rsub__(self, other):\n if type(other) is complex:\n return complex(other.real - self.real, other.imag - self.imag)\n if type(other) in (int, float):\n return complex(other - self.real, -self.imag)\n return NotImplemented\n \n def __mul__(self, other):\n if type(other) is complex:\n return complex(self.real * other.real - self.imag * other.imag,\n self.real * other.imag + self.imag * other.real)\n if type(other) in (int, float):\n return complex(self.real * other, self.imag * other)\n return NotImplemented\n \n def __rmul__(self, other):\n return self.__mul__(other)\n \n def __truediv__(self, other):\n if type(other) is complex:\n denominator = other.real ** 2 + other.imag ** 2\n real_part = (self.real * other.real + self.imag * other.imag) / denominator\n imag_part = (self.imag * other.real - self.real * other.imag) / denominator\n return complex(real_part, imag_part)\n if type(other) in (int, float):\n return complex(self.real / other, self.imag / other)\n return NotImplemented\n \n def __pow__(self, other: int | float):\n if type(other) in (int, float):\n return complex(self.__abs__() ** other * math.cos(other * phase(self)),\n self.__abs__() ** other * math.sin(other * phase(self)))\n return NotImplemented\n \n def __abs__(self) -> float:\n return math.sqrt(self.real ** 2 + self.imag ** 2)\n\n def __neg__(self):\n return complex(-self.real, -self.imag)\n \n def __hash__(self):\n return hash((self.real, self.imag))\n\n\n# Conversions to and from polar coordinates\n\ndef phase(z: complex):\n return math.atan2(z.imag, z.real)\n\ndef polar(z: complex):\n return z.__abs__(), phase(z)\n\ndef rect(r: float, phi: float):\n return r * math.cos(phi) + r * math.sin(phi) * 1j\n\n# Power and logarithmic functions\n\ndef exp(z: complex):\n return math.exp(z.real) * rect(1, z.imag)\n\ndef log(z: complex, base=2.718281828459045):\n return math.log(z.__abs__(), base) + phase(z) * 1j\n\ndef log10(z: complex):\n return log(z, 10)\n\ndef sqrt(z: complex):\n return z ** 0.5\n\n# Trigonometric functions\n\ndef acos(z: complex):\n return -1j * log(z + sqrt(z * z - 1))\n\ndef asin(z: complex):\n return -1j * log(1j * z + sqrt(1 - z * z))\n\ndef atan(z: complex):\n return 1j / 2 * log((1 - 1j * z) / (1 + 1j * z))\n\ndef cos(z: complex):\n return (exp(z) + exp(-z)) / 2\n\ndef sin(z: complex):\n return (exp(z) - exp(-z)) / (2 * 1j)\n\ndef tan(z: complex):\n return sin(z) / cos(z)\n\n# Hyperbolic functions\n\ndef acosh(z: complex):\n return log(z + sqrt(z * z - 1))\n\ndef asinh(z: complex):\n return log(z + sqrt(z * z + 1))\n\ndef atanh(z: complex):\n return 1 / 2 * log((1 + z) / (1 - z))\n\ndef cosh(z: complex):\n return (exp(z) + exp(-z)) / 2\n\ndef sinh(z: complex):\n return (exp(z) - exp(-z)) / 2\n\ndef tanh(z: complex):\n return sinh(z) / cosh(z)\n\n# Classification functions\n\ndef isfinite(z: complex):\n return math.isfinite(z.real) and math.isfinite(z.imag)\n\ndef isinf(z: complex):\n return math.isinf(z.real) or math.isinf(z.imag)\n\ndef isnan(z: complex):\n return math.isnan(z.real) or math.isnan(z.imag)\n\ndef isclose(a: complex, b: complex):\n return math.isclose(a.real, b.real) and math.isclose(a.imag, b.imag)\n\n# Constants\n\npi = math.pi\ne = math.e\ntau = 2 * pi\ninf = math.inf\ninfj = complex(0, inf)\nnan = math.nan\nnanj = complex(0, nan)\n"; -const char kPythonLibs_collections[] = "from typing import TypeVar, Iterable\n\ndef Counter[T](iterable: Iterable[T]):\n a: dict[T, int] = {}\n for x in iterable:\n if x in a:\n a[x] += 1\n else:\n a[x] = 1\n return a\n\n\nclass defaultdict(dict):\n def __init__(self, default_factory, *args):\n super().__init__(*args)\n self.default_factory = default_factory\n\n def __missing__(self, key):\n self[key] = self.default_factory()\n return self[key]\n\n def __repr__(self) -> str:\n return f\"defaultdict({self.default_factory}, {super().__repr__()})\"\n\n def copy(self):\n return defaultdict(self.default_factory, self)\n\n\nclass deque[T]:\n _data: list[T]\n _head: int\n _tail: int\n _capacity: int\n\n def __init__(self, iterable: Iterable[T] = None):\n self._data = [None] * 8 # type: ignore\n self._head = 0\n self._tail = 0\n self._capacity = len(self._data)\n\n if iterable is not None:\n self.extend(iterable)\n\n def __resize_2x(self):\n backup = list(self)\n self._capacity *= 2\n self._head = 0\n self._tail = len(backup)\n self._data.clear()\n self._data.extend(backup)\n self._data.extend([None] * (self._capacity - len(backup)))\n\n def append(self, x: T):\n self._data[self._tail] = x\n self._tail = (self._tail + 1) % self._capacity\n if (self._tail + 1) % self._capacity == self._head:\n self.__resize_2x()\n\n def appendleft(self, x: T):\n self._head = (self._head - 1) % self._capacity\n self._data[self._head] = x\n if (self._tail + 1) % self._capacity == self._head:\n self.__resize_2x()\n\n def copy(self):\n return deque(self)\n \n def count(self, x: T) -> int:\n n = 0\n for item in self:\n if item == x:\n n += 1\n return n\n \n def extend(self, iterable: Iterable[T]):\n for x in iterable:\n self.append(x)\n\n def extendleft(self, iterable: Iterable[T]):\n for x in iterable:\n self.appendleft(x)\n \n def pop(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n self._tail = (self._tail - 1) % self._capacity\n return self._data[self._tail]\n \n def popleft(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n x = self._data[self._head]\n self._head = (self._head + 1) % self._capacity\n return x\n \n def clear(self):\n i = self._head\n while i != self._tail:\n self._data[i] = None # type: ignore\n i = (i + 1) % self._capacity\n self._head = 0\n self._tail = 0\n\n def rotate(self, n: int = 1):\n if len(self) == 0:\n return\n if n > 0:\n n = n % len(self)\n for _ in range(n):\n self.appendleft(self.pop())\n elif n < 0:\n n = -n % len(self)\n for _ in range(n):\n self.append(self.popleft())\n\n def __len__(self) -> int:\n return (self._tail - self._head) % self._capacity\n\n def __contains__(self, x: object) -> bool:\n for item in self:\n if item == x:\n return True\n return False\n \n def __iter__(self):\n i = self._head\n while i != self._tail:\n yield self._data[i]\n i = (i + 1) % self._capacity\n\n def __eq__(self, other: object) -> bool:\n if not isinstance(other, deque):\n return NotImplemented\n if len(self) != len(other):\n return False\n for x, y in zip(self, other):\n if x != y:\n return False\n return True\n \n def __ne__(self, other: object) -> bool:\n if not isinstance(other, deque):\n return NotImplemented\n return not self == other\n \n def __repr__(self) -> str:\n return f\"deque({list(self)!r})\"\n\n"; +const char kPythonLibs_collections[] = "from typing import TypeVar, Iterable\n\ndef Counter[T](iterable: Iterable[T]):\n a: dict[T, int] = {}\n for x in iterable:\n if x in a:\n a[x] += 1\n else:\n a[x] = 1\n return a\n\n\nclass defaultdict(dict):\n def __init__(self, default_factory, *args):\n super().__init__(*args)\n self.default_factory = default_factory\n\n def __missing__(self, key):\n self[key] = self.default_factory()\n return self[key]\n\n def __repr__(self) -> str:\n return f\"defaultdict({self.default_factory}, {super().__repr__()})\"\n\n def copy(self):\n return defaultdict(self.default_factory, self)\n\n\nclass deque[T]:\n _head: int\n _tail: int\n _maxlen: int | None\n _capacity: int\n _data: list[T]\n\n def __init__(self, iterable: Iterable[T] = None, maxlen: int | None = None):\n if maxlen is not None:\n assert maxlen > 0\n\n self._head = 0\n self._tail = 0\n self._maxlen = maxlen\n self._capacity = 8 if maxlen is None else maxlen + 1\n self._data = [None] * self._capacity # type: ignore\n\n if iterable is not None:\n self.extend(iterable)\n\n @property\n def maxlen(self) -> int | None:\n return self._maxlen\n\n def __resize_2x(self):\n backup = list(self)\n self._capacity *= 2\n self._head = 0\n self._tail = len(backup)\n self._data.clear()\n self._data.extend(backup)\n self._data.extend([None] * (self._capacity - len(backup)))\n\n def append(self, x: T):\n if (self._tail + 1) % self._capacity == self._head:\n if self._maxlen is None:\n self.__resize_2x()\n else:\n self.popleft()\n self._data[self._tail] = x\n self._tail = (self._tail + 1) % self._capacity\n\n def appendleft(self, x: T):\n if (self._tail + 1) % self._capacity == self._head:\n if self._maxlen is None:\n self.__resize_2x()\n else:\n self.pop()\n self._head = (self._head - 1) % self._capacity\n self._data[self._head] = x\n\n def copy(self):\n return deque(self, maxlen=self.maxlen)\n \n def count(self, x: T) -> int:\n n = 0\n for item in self:\n if item == x:\n n += 1\n return n\n \n def extend(self, iterable: Iterable[T]):\n for x in iterable:\n self.append(x)\n\n def extendleft(self, iterable: Iterable[T]):\n for x in iterable:\n self.appendleft(x)\n \n def pop(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n self._tail = (self._tail - 1) % self._capacity\n x = self._data[self._tail]\n self._data[self._tail] = None\n return x\n \n def popleft(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n x = self._data[self._head]\n self._data[self._head] = None\n self._head = (self._head + 1) % self._capacity\n return x\n \n def clear(self):\n i = self._head\n while i != self._tail:\n self._data[i] = None # type: ignore\n i = (i + 1) % self._capacity\n self._head = 0\n self._tail = 0\n\n def rotate(self, n: int = 1):\n if len(self) == 0:\n return\n if n > 0:\n n = n % len(self)\n for _ in range(n):\n self.appendleft(self.pop())\n elif n < 0:\n n = -n % len(self)\n for _ in range(n):\n self.append(self.popleft())\n\n def __len__(self) -> int:\n return (self._tail - self._head) % self._capacity\n\n def __contains__(self, x: object) -> bool:\n for item in self:\n if item == x:\n return True\n return False\n \n def __iter__(self):\n i = self._head\n while i != self._tail:\n yield self._data[i]\n i = (i + 1) % self._capacity\n\n def __eq__(self, other: object) -> bool:\n if not isinstance(other, deque):\n return NotImplemented\n if len(self) != len(other):\n return False\n for x, y in zip(self, other):\n if x != y:\n return False\n return True\n \n def __ne__(self, other: object) -> bool:\n if not isinstance(other, deque):\n return NotImplemented\n return not self == other\n \n def __repr__(self) -> str:\n if self.maxlen is None:\n return f\"deque({list(self)!r})\"\n return f\"deque({list(self)!r}, maxlen={self.maxlen})\"\n\n"; const char kPythonLibs_dataclasses[] = "def _get_annotations(cls: type):\n inherits = []\n while cls is not object:\n inherits.append(cls)\n cls = cls.__base__\n inherits.reverse()\n res = {}\n for cls in inherits:\n res.update(cls.__annotations__)\n return res.keys()\n\ndef _wrapped__init__(self, *args, **kwargs):\n cls = type(self)\n cls_d = cls.__dict__\n fields = _get_annotations(cls)\n i = 0 # index into args\n for field in fields:\n if field in kwargs:\n setattr(self, field, kwargs.pop(field))\n else:\n if i < len(args):\n setattr(self, field, args[i])\n i += 1\n elif field in cls_d: # has default value\n setattr(self, field, cls_d[field])\n else:\n raise TypeError(f\"{cls.__name__} missing required argument {field!r}\")\n if len(args) > i:\n raise TypeError(f\"{cls.__name__} takes {len(fields)} positional arguments but {len(args)} were given\")\n if len(kwargs) > 0:\n raise TypeError(f\"{cls.__name__} got an unexpected keyword argument {next(iter(kwargs))!r}\")\n\ndef _wrapped__repr__(self):\n fields = _get_annotations(type(self))\n obj_d = self.__dict__\n args: list = [f\"{field}={obj_d[field]!r}\" for field in fields]\n return f\"{type(self).__name__}({', '.join(args)})\"\n\ndef _wrapped__eq__(self, other):\n if type(self) is not type(other):\n return False\n fields = _get_annotations(type(self))\n for field in fields:\n if getattr(self, field) != getattr(other, field):\n return False\n return True\n\ndef _wrapped__ne__(self, other):\n return not self.__eq__(other)\n\ndef dataclass(cls: type):\n assert type(cls) is type\n cls_d = cls.__dict__\n if '__init__' not in cls_d:\n cls.__init__ = _wrapped__init__\n if '__repr__' not in cls_d:\n cls.__repr__ = _wrapped__repr__\n if '__eq__' not in cls_d:\n cls.__eq__ = _wrapped__eq__\n if '__ne__' not in cls_d:\n cls.__ne__ = _wrapped__ne__\n fields = _get_annotations(cls)\n has_default = False\n for field in fields:\n if field in cls_d:\n has_default = True\n else:\n if has_default:\n raise TypeError(f\"non-default argument {field!r} follows default argument\")\n return cls\n\ndef asdict(obj) -> dict:\n fields = _get_annotations(type(obj))\n obj_d = obj.__dict__\n return {field: obj_d[field] for field in fields}"; const char kPythonLibs_datetime[] = "from time import localtime\nimport operator\n\nclass timedelta:\n def __init__(self, days=0, seconds=0):\n self.days = days\n self.seconds = seconds\n\n def __repr__(self):\n return f\"datetime.timedelta(days={self.days}, seconds={self.seconds})\"\n\n def __eq__(self, other) -> bool:\n if not isinstance(other, timedelta):\n return NotImplemented\n return (self.days, self.seconds) == (other.days, other.seconds)\n\n def __ne__(self, other) -> bool:\n if not isinstance(other, timedelta):\n return NotImplemented\n return (self.days, self.seconds) != (other.days, other.seconds)\n\n\nclass date:\n def __init__(self, year: int, month: int, day: int):\n self.year = year\n self.month = month\n self.day = day\n\n @staticmethod\n def today():\n t = localtime()\n return date(t.tm_year, t.tm_mon, t.tm_mday)\n \n def __cmp(self, other, op):\n if not isinstance(other, date):\n return NotImplemented\n if self.year != other.year:\n return op(self.year, other.year)\n if self.month != other.month:\n return op(self.month, other.month)\n return op(self.day, other.day)\n\n def __eq__(self, other) -> bool:\n return self.__cmp(other, operator.eq)\n \n def __ne__(self, other) -> bool:\n return self.__cmp(other, operator.ne)\n\n def __lt__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.lt)\n\n def __le__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.le)\n\n def __gt__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.gt)\n\n def __ge__(self, other: 'date') -> bool:\n return self.__cmp(other, operator.ge)\n\n def __str__(self):\n return f\"{self.year}-{self.month:02}-{self.day:02}\"\n\n def __repr__(self):\n return f\"datetime.date({self.year}, {self.month}, {self.day})\"\n\n\nclass datetime(date):\n def __init__(self, year: int, month: int, day: int, hour: int, minute: int, second: int):\n super().__init__(year, month, day)\n # Validate and set hour, minute, and second\n if not 0 <= hour <= 23:\n raise ValueError(\"Hour must be between 0 and 23\")\n self.hour = hour\n if not 0 <= minute <= 59:\n raise ValueError(\"Minute must be between 0 and 59\")\n self.minute = minute\n if not 0 <= second <= 59:\n raise ValueError(\"Second must be between 0 and 59\")\n self.second = second\n\n def date(self) -> date:\n return date(self.year, self.month, self.day)\n\n @staticmethod\n def now():\n t = localtime()\n tm_sec = t.tm_sec\n if tm_sec == 60:\n tm_sec = 59\n return datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, tm_sec)\n\n def __str__(self):\n return f\"{self.year}-{self.month:02}-{self.day:02} {self.hour:02}:{self.minute:02}:{self.second:02}\"\n\n def __repr__(self):\n return f\"datetime.datetime({self.year}, {self.month}, {self.day}, {self.hour}, {self.minute}, {self.second})\"\n\n def __cmp(self, other, op):\n if not isinstance(other, datetime):\n return NotImplemented\n if self.year != other.year:\n return op(self.year, other.year)\n if self.month != other.month:\n return op(self.month, other.month)\n if self.day != other.day:\n return op(self.day, other.day)\n if self.hour != other.hour:\n return op(self.hour, other.hour)\n if self.minute != other.minute:\n return op(self.minute, other.minute)\n return op(self.second, other.second)\n\n def __eq__(self, other) -> bool:\n return self.__cmp(other, operator.eq)\n \n def __ne__(self, other) -> bool:\n return self.__cmp(other, operator.ne)\n \n def __lt__(self, other) -> bool:\n return self.__cmp(other, operator.lt)\n \n def __le__(self, other) -> bool:\n return self.__cmp(other, operator.le)\n \n def __gt__(self, other) -> bool:\n return self.__cmp(other, operator.gt)\n \n def __ge__(self, other) -> bool:\n return self.__cmp(other, operator.ge)\n\n\n"; const char kPythonLibs_functools[] = "class cache:\n def __init__(self, f):\n self.f = f\n self.cache = {}\n\n def __call__(self, *args):\n if args not in self.cache:\n self.cache[args] = self.f(*args)\n return self.cache[args]\n \nclass lru_cache:\n def __init__(self, maxsize=128):\n self.maxsize = maxsize\n self.cache = {}\n\n def __call__(self, f):\n def wrapped(*args):\n if args in self.cache:\n res = self.cache.pop(args)\n self.cache[args] = res\n return res\n \n res = f(*args)\n if len(self.cache) >= self.maxsize:\n first_key = next(iter(self.cache))\n self.cache.pop(first_key)\n self.cache[args] = res\n return res\n return wrapped\n \ndef reduce(function, sequence, initial=...):\n it = iter(sequence)\n if initial is ...:\n try:\n value = next(it)\n except StopIteration:\n raise TypeError(\"reduce() of empty sequence with no initial value\")\n else:\n value = initial\n for element in it:\n value = function(value, element)\n return value\n\nclass partial:\n def __init__(self, f, *args, **kwargs):\n self.f = f\n if not callable(f):\n raise TypeError(\"the first argument must be callable\")\n self.args = args\n self.kwargs = kwargs\n\n def __call__(self, *args, **kwargs):\n kwargs.update(self.kwargs)\n return self.f(*self.args, *args, **kwargs)\n\n"; diff --git a/tests/72_collections.py b/tests/72_collections.py index 5c75782e..07fa888b 100644 --- a/tests/72_collections.py +++ b/tests/72_collections.py @@ -516,3 +516,31 @@ d = deque() for i in range(100): d.append(1) gc.collect() + +# test maxlen +q = deque(maxlen=3) +assertEqual(q.maxlen, 3) +q.append(1) +q.append(2) +q.append(3) +assertEqual(list(q), [1, 2, 3]) +assertEqual(q._data, [1, 2, 3, None]) +q.append(4) +assertEqual(list(q), [2, 3, 4]) +assertEqual(q._data, [None, 2, 3, 4]) +q.appendleft(1) +assertEqual(list(q), [1, 2, 3]) +assertEqual(q._data, [1, 2, 3, None]) +q.appendleft(0) +assertEqual(list(q), [0, 1, 2]) +assertEqual(q._data, [1, 2, None, 0]) +q.pop() +assertEqual(list(q), [0, 1]) +assertEqual(q._data, [1, None, None, 0]) +assertEqual(len(q), 2) +assertEqual(q._capacity, 4) +q.popleft() +assertEqual(list(q), [1]) +assertEqual(q._data, [1, None, None, None]) + + From 6cb47cb5447747b139a79a687cb00811300489b3 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 9 Sep 2025 19:15:15 +0800 Subject: [PATCH 6/7] Update retype.yml --- docs/retype.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/retype.yml b/docs/retype.yml index 7d05eb7e..67d69a05 100644 --- a/docs/retype.yml +++ b/docs/retype.yml @@ -3,7 +3,7 @@ output: .retype url: https://pocketpy.dev branding: title: pocketpy - label: v2.1.1 + label: v2.1.2 logo: "./static/logo.png" favicon: "./static/logo.png" meta: From 4ac11583bab00fb94919b70bbb7547105155f260 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 9 Sep 2025 19:16:09 +0800 Subject: [PATCH 7/7] Update debugging.md --- docs/features/debugging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/debugging.md b/docs/features/debugging.md index 2bac8f62..34ee807e 100644 --- a/docs/features/debugging.md +++ b/docs/features/debugging.md @@ -30,7 +30,7 @@ pocketpy provides a C-API `py_debugger_waitforattach`, which starts a debug server and waits for the VSCode extension to attach. When the debugger is attached, the program will continue to run. -+ If you are using pocketpy's standalone executable `main.exe`, you can pass `--debug` flag to it. This will automatically call `py_debugger_waitforattach("localhost", 6110)` before running your program. ++ If you are using pocketpy's standalone executable `main.exe`, you can pass `--debug` flag to it. This will automatically call `py_debugger_waitforattach("127.0.0.1", 6110)` before running your program. + If you are embedding pocketpy as a library, you need to call `py_debugger_waitforattach` manually in your C/C++ code. ## Configuration