From 48b70c944e5b3723101e39868d3b291dd8393763 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 26 Jan 2026 17:50:03 +0800 Subject: [PATCH] fix #426 --- docs/retype.yml | 2 +- include/pocketpy/config.h | 4 +- plugins/flutter/pocketpy/ios/pocketpy.podspec | 2 +- .../flutter/pocketpy/macos/pocketpy.podspec | 2 +- plugins/flutter/pocketpy/pubspec.yaml | 2 +- plugins/flutter/pocketpy/src/CMakeLists.txt | 2 +- src/bindings/py_str.c | 111 ++++++++++++++++++ tests/042_str_mod.py | 51 ++++++++ 8 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 tests/042_str_mod.py diff --git a/docs/retype.yml b/docs/retype.yml index d2613367..f680017f 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.7 + label: v2.1.8 logo: "./static/logo.png" favicon: "./static/logo.png" meta: diff --git a/include/pocketpy/config.h b/include/pocketpy/config.h index 8f5d42ea..deaaac47 100644 --- a/include/pocketpy/config.h +++ b/include/pocketpy/config.h @@ -1,10 +1,10 @@ #pragma once // clang-format off -#define PK_VERSION "2.1.7" +#define PK_VERSION "2.1.8" #define PK_VERSION_MAJOR 2 #define PK_VERSION_MINOR 1 -#define PK_VERSION_PATCH 7 +#define PK_VERSION_PATCH 8 /*************** feature settings ***************/ #ifndef PK_ENABLE_OS // can be overridden by cmake diff --git a/plugins/flutter/pocketpy/ios/pocketpy.podspec b/plugins/flutter/pocketpy/ios/pocketpy.podspec index ce2eee34..17ac01fa 100644 --- a/plugins/flutter/pocketpy/ios/pocketpy.podspec +++ b/plugins/flutter/pocketpy/ios/pocketpy.podspec @@ -35,7 +35,7 @@ A new Flutter FFI plugin project. s.prepare_command = <<-CMD rm -rf pocketpy - git clone --branch v2.1.7 --depth 1 https://github.com/pocketpy/pocketpy.git + git clone --branch v2.1.8 --depth 1 https://github.com/pocketpy/pocketpy.git cd pocketpy git submodule update --init --recursive --depth 1 bash build_ios_libs.sh diff --git a/plugins/flutter/pocketpy/macos/pocketpy.podspec b/plugins/flutter/pocketpy/macos/pocketpy.podspec index 57b9087e..14addc98 100644 --- a/plugins/flutter/pocketpy/macos/pocketpy.podspec +++ b/plugins/flutter/pocketpy/macos/pocketpy.podspec @@ -32,7 +32,7 @@ A new Flutter FFI plugin project. s.prepare_command = <<-CMD rm -rf pocketpy - git clone --branch v2.1.7 --depth 1 https://github.com/pocketpy/pocketpy.git + git clone --branch v2.1.8 --depth 1 https://github.com/pocketpy/pocketpy.git cd pocketpy git submodule update --init --recursive --depth 1 bash build_darwin_libs.sh diff --git a/plugins/flutter/pocketpy/pubspec.yaml b/plugins/flutter/pocketpy/pubspec.yaml index 72f6058d..429f2a9b 100644 --- a/plugins/flutter/pocketpy/pubspec.yaml +++ b/plugins/flutter/pocketpy/pubspec.yaml @@ -1,6 +1,6 @@ name: pocketpy description: A lightweight Python interpreter for game engines. It supports Android/iOS/Windows/Linux/MacOS. -version: 2.1.7 +version: 2.1.8 homepage: https://pocketpy.dev repository: https://github.com/pocketpy/pocketpy diff --git a/plugins/flutter/pocketpy/src/CMakeLists.txt b/plugins/flutter/pocketpy/src/CMakeLists.txt index 31fb767f..fa777305 100644 --- a/plugins/flutter/pocketpy/src/CMakeLists.txt +++ b/plugins/flutter/pocketpy/src/CMakeLists.txt @@ -21,7 +21,7 @@ set(PK_BUILD_SHARED_LIB ON CACHE BOOL "" FORCE) FetchContent_Declare( pocketpy GIT_REPOSITORY https://github.com/pocketpy/pocketpy.git - GIT_TAG v2.1.7 + GIT_TAG v2.1.8 ) FetchContent_MakeAvailable(pocketpy) diff --git a/src/bindings/py_str.c b/src/bindings/py_str.c index 97892953..8c8737f8 100644 --- a/src/bindings/py_str.c +++ b/src/bindings/py_str.c @@ -41,6 +41,116 @@ static bool str__len__(int argc, py_Ref argv) { return true; } +static bool str__mod__(int argc, py_Ref argv) { + PY_CHECK_ARGC(2); + c11_string* self = pk_tostr(&argv[0]); + // %s + // %d %i + // %f + // %r + // %% + py_TValue* args; + int args_count; + if(py_istype(&argv[1], tp_tuple)) { + args_count = py_tuple_len(&argv[1]); + args = py_tuple_data(&argv[1]); + } else { + args_count = 1; + args = &argv[1]; + } + + int arg_index = 0; + const char* p = self->data; + const char* p_end = self->data + self->size; + + c11_sbuf buf; + c11_sbuf__ctor(&buf); + + while(p < p_end) { + if(*p == '%') { + p++; + if(p >= p_end) { + c11_sbuf__dtor(&buf); + return ValueError("incomplete format"); + } + char spec = *p; + p++; + if(spec == '%') { + // %% -> % + c11_sbuf__write_char(&buf, '%'); + } else if(spec == 's') { + // %s -> string + if(arg_index >= args_count) { + c11_sbuf__dtor(&buf); + return TypeError("not enough arguments for format string"); + } + if(!py_str(&args[arg_index])) { + c11_sbuf__dtor(&buf); + return false; + } + c11_sbuf__write_sv(&buf, py_tosv(py_retval())); + arg_index++; + } else if(spec == 'd' || spec == 'i') { + // %d or %i -> integer + if(arg_index >= args_count) { + c11_sbuf__dtor(&buf); + return TypeError("not enough arguments for format string"); + } + if(!py_checktype(&args[arg_index], tp_int)) { + c11_sbuf__dtor(&buf); + return false; + } + py_i64 val = py_toint(&args[arg_index]); + c11_sbuf__write_i64(&buf, val); + arg_index++; + } else if(spec == 'f') { + // %f -> float + if(arg_index >= args_count) { + c11_sbuf__dtor(&buf); + return TypeError("not enough arguments for format string"); + } + py_f64 val; + if(py_istype(&args[arg_index], tp_float)) { + val = py_tofloat(&args[arg_index]); + } else if(py_istype(&args[arg_index], tp_int)) { + val = (py_f64)py_toint(&args[arg_index]); + } else { + c11_sbuf__dtor(&buf); + return TypeError("a float is required"); + } + c11_sbuf__write_f64(&buf, val, 6); + arg_index++; + } else if(spec == 'r') { + // %r -> repr + if(arg_index >= args_count) { + c11_sbuf__dtor(&buf); + return TypeError("not enough arguments for format string"); + } + if(!py_repr(&args[arg_index])) { + c11_sbuf__dtor(&buf); + return false; + } + c11_sbuf__write_sv(&buf, py_tosv(py_retval())); + arg_index++; + } else { + c11_sbuf__dtor(&buf); + return ValueError("unsupported format character '%c'", spec); + } + } else { + c11_sbuf__write_char(&buf, *p); + p++; + } + } + + if(arg_index != args_count) { + c11_sbuf__dtor(&buf); + return TypeError("not all arguments converted during string formatting"); + } + + c11_sbuf__py_submit(&buf, py_retval()); + return true; +} + static bool str__add__(int argc, py_Ref argv) { PY_CHECK_ARGC(2); c11_string* self = pk_tostr(&argv[0]); @@ -503,6 +613,7 @@ py_Type pk_str__register() { py_bindmagic(tp_str, __new__, str__new__); py_bindmagic(tp_str, __hash__, str__hash__); py_bindmagic(tp_str, __len__, str__len__); + py_bindmagic(tp_str, __mod__, str__mod__); py_bindmagic(tp_str, __add__, str__add__); py_bindmagic(tp_str, __mul__, str__mul__); py_bindmagic(tp_str, __rmul__, str__rmul__); diff --git a/tests/042_str_mod.py b/tests/042_str_mod.py new file mode 100644 index 00000000..a2e0761a --- /dev/null +++ b/tests/042_str_mod.py @@ -0,0 +1,51 @@ +# Test str.__mod__ (old-style % formatting) + +# Test %s - string formatting +assert "hello %s" % "world" == "hello world" +assert "%s" % 123 == "123" +assert "%s %s" % ("a", "b") == "a b" +assert "name: %s, age: %s" % ("Alice", 30) == "name: Alice, age: 30" + +# Test %d - integer formatting +assert "%d" % 42 == "42" +assert "%d" % -123 == "-123" +assert "%d" % 0 == "0" +assert "count: %d" % 100 == "count: 100" + +# Test %i - integer formatting (same as %d) +assert "%i" % 42 == "42" +assert "%i" % -123 == "-123" +assert "value: %i" % 999 == "value: 999" + +# Test %f - float formatting +assert "%f" % 3.14 == "3.140000" +assert "%f" % -2.5 == "-2.500000" +assert "%f" % 0.0 == "0.000000" +assert "%f" % 42 == "42.000000" # int to float + +# Test %r - repr formatting +assert "%r" % "hello" == "'hello'" +assert "%r" % 123 == "123" +assert "%r" % [1, 2, 3] == "[1, 2, 3]" + +# Test %% - literal percent +assert "%%" % () == "%" +assert "100%%" % () == "100%" +assert "%%s" % () == "%s" +assert "%d%%" % 50 == "50%" + +# Test combined format specifiers +assert "%s is %d years old" % ("Bob", 25) == "Bob is 25 years old" +assert "%s: %f" % ("pi", 3.14159) == "pi: 3.141590" +assert "%d + %d = %d" % (1, 2, 3) == "1 + 2 = 3" +assert "Hello %s! You have %d messages." % ("User", 5) == "Hello User! You have 5 messages." + +# Test single value (not tuple) +assert "value: %s" % "test" == "value: test" +assert "number: %d" % 42 == "number: 42" + +# Test empty string +assert "" % () == "" + +# Test no format specifiers +assert "hello world" % () == "hello world"