Compare commits

...

12 Commits

Author SHA1 Message Date
blueloveTH
b25cc37123 Merge branch 'main' into sandbox 2026-07-22 13:21:31 +08:00
blueloveTH
2161532677 fix 2026-07-22 13:21:26 +08:00
blueloveTH
048df25e6f add interfaces 2026-07-22 13:11:45 +08:00
blueloveTH
beb1557d00 bump to v2.2.0 2026-07-22 12:49:00 +08:00
BLUELOVETH
4e2b40db2d
test fastpath for self.* read (#535)
* test fastpath for `self.*` read

* bak
2026-07-22 12:36:58 +08:00
Md Kaif
7fd7e2db15
fix(float): abs(-0.0) should return 0.0 (#534)
abs(-0.0) returns -0.0 instead of 0.0, and math.fabs(-0.0) does the
same. Both come from the idiom (x < 0) ? -x : x. Since -0.0 < 0 is
false, the negative zero is returned unchanged.

The idiom appears twice, once in dmath_fabs and once open-coded in
float__abs__. Fix dmath_fabs by clearing the sign bit, the same way
dmath_copysign just above it works, and make float__abs__ call the
shared helper instead of repeating the comparison.

abs(-0.0) == 0.0 is true even with the bug, because -0.0 == 0.0 under
IEEE 754, so the added tests compare str(...) to check the sign.
2026-07-21 19:36:08 +08:00
Md Kaif
005550e92a
fix(str): zfill should preserve a leading sign (#533)
zfill writes the padding zeros before the whole string, so a leading
+ or - ends up in the middle of the result instead of staying in front.
For example "-5".zfill(4) gave '00-5' where CPython gives '-005'.

CPython inserts the padding after the sign character, and the sign
counts toward the requested width. Write the sign first, then the
zeros, then the rest of the string. delta is still computed before
this, so the total length is unchanged.

The existing tests only covered unsigned strings, which is why this
went unnoticed. Added three asserts alongside them.
2026-07-21 19:09:11 +08:00
blueloveTH
f2b6d90a72 Update mpack1.h 2026-07-21 14:30:09 +08:00
blueloveTH
2ed21e8814 Update bindings.c 2026-07-21 14:27:31 +08:00
blueloveTH
08fce73bbb Update mpack1.h 2026-07-21 14:27:18 +08:00
blueloveTH
4fc4d665ec improve mpack 2026-07-21 14:09:27 +08:00
blueloveTH
4816c4471b Update bindings.c 2026-07-20 21:18:18 +08:00
23 changed files with 201 additions and 31 deletions

View File

@ -251,7 +251,7 @@
* types in @ref docs/protocol.md for more information.
*/
#ifndef MPACK_EXTENSIONS
#define MPACK_EXTENSIONS 0
#define MPACK_EXTENSIONS 1
#endif
/**

View File

@ -0,0 +1,20 @@
#pragma once
#include "mpack.h"
#include "pocketpy.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef bool (*is_mpack_ext_t)(py_Type type);
typedef bool (*py_to_mpack_ext_t)(py_Ref object, mpack_writer_t* writer);
typedef bool (*mpack_to_py_ext_t)(py_StackRef tmp, mpack_node_t node);
void mpack_config_exttype_callbacks(is_mpack_ext_t is_ext,
py_to_mpack_ext_t to_mpack,
mpack_to_py_ext_t to_py);
#ifdef __cplusplus
}
#endif

View File

@ -1,8 +1,22 @@
#include "pocketpy.h"
#include "mpack.h"
#include "mpack1.h"
#include <assert.h>
_Static_assert(MPACK_EXTENSIONS == 1, "You should change MPACK_EXTENSIONS in mpack.h to 1");
static is_mpack_ext_t is_mpack_ext;
static py_to_mpack_ext_t py_to_mpack_ext;
static mpack_to_py_ext_t mpack_to_py_ext;
void mpack_config_exttype_callbacks(is_mpack_ext_t is_ext,
py_to_mpack_ext_t to_mpack,
mpack_to_py_ext_t to_py) {
is_mpack_ext = is_ext;
py_to_mpack_ext = to_mpack;
mpack_to_py_ext = to_py;
}
static bool mpack_to_py(mpack_node_t node) {
py_StackRef tmp = py_pushtmp();
@ -69,6 +83,14 @@ static bool mpack_to_py(mpack_node_t node) {
}
break;
}
case mpack_type_ext: {
if(!mpack_to_py_ext) {
return RuntimeError("msgpack: mpack_to_py_ext is not set, cannot handle ext type");
}
if(!mpack_to_py_ext(tmp, node)) return false;
break;
}
default: return ValueError("msgpack: invalid node type");
}
return true;
@ -111,9 +133,7 @@ static bool mpack_write_dict_kv(py_Ref k, py_Ref v, void* ctx) {
} else {
return TypeError("msgpack: key must be string or integer");
}
bool ok = py_to_mpack(v, writer);
if(!ok) mpack_write_nil(writer);
return ok;
return py_to_mpack(v, writer);
}
static bool py_to_mpack(py_Ref object, mpack_writer_t* writer) {
@ -154,7 +174,15 @@ static bool py_to_mpack(py_Ref object, mpack_writer_t* writer) {
if(!ok) return false;
break;
}
default: return TypeError("msgpack: unsupported type '%t'", object->type);
default: {
if(is_mpack_ext && is_mpack_ext(object->type)) {
assert(py_to_mpack_ext != NULL);
return py_to_mpack_ext(object, writer);
break;
}
mpack_write_nil(writer);
return TypeError("msgpack: unsupported type '%t'", object->type);
}
}
return true;
}

View File

@ -195,6 +195,7 @@ And these are the results of the primes benchmark on Intel i5-12400F, WSL (Ubunt
| | Description |
|-----------------------------------------------------------------|--------------------------------------------------------------------------|
| [Tesselmax.EM](https://fdtd.io) | Distributed electromagnetic field simulator for silicon photonics and RF/interconnect design. |
| [godot-pocketpy](https://github.com/pocketpy/godot-pocketpy) | Godot extension for using pocketpy in Godot Engine. |
| [TIC-80](https://github.com/nesbox/TIC-80) | TIC-80 is a fantasy computer for making, playing and sharing tiny games. |
| [py-js](https://github.com/shakfu/py-js) | Python3 externals for Max / MSP. |
@ -215,7 +216,7 @@ All kinds of contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTI
- any suggestions
- any questions
If you find pkpy useful, consider star this repository (●'◡'●)
If you find pkpy useful, consider star this repository.
## Sponsor this project
@ -226,6 +227,17 @@ You can sponsor this project via these ways.
Your sponsorship will help us develop pkpy continuously.
## Premium supporter
Special thanks to our premium supporter, Tesselmax, for sponsoring the development of pocketpy.
Tesselmax builds Tesselmax.EM, a distributed electromagnetic field simulator for silicon
photonics and RF/interconnect design (FDTD, FDFD, and mode solving across GPU clusters).
Tesselmax embeds pocketpy as the scripting engine in our simulation console, so user scripts drive
geometry, materials, sources, and post-processing.
Website: [https://fdtd.io](https://fdtd.io)
## Reference
+ [cpython](https://github.com/python/cpython)

View File

@ -51,7 +51,7 @@ On Windows platform, only MSVC compiler is officially supported.
## Star the repo
If you find pkpy useful, consider [star this repository](https://github.com/blueloveth/pocketpy) (●'◡'●)
If you find pkpy useful, consider [star this repository](https://github.com/blueloveth/pocketpy).
## Sponsor this project
@ -62,6 +62,17 @@ You can sponsor this project via these ways.
Your sponsorship will help us develop pkpy continuously.
## Premium supporter
Special thanks to our premium supporter, Tesselmax, for sponsoring the development of pocketpy.
Tesselmax builds Tesselmax.EM, a distributed electromagnetic field simulator for silicon
photonics and RF/interconnect design (FDTD, FDFD, and mode solving across GPU clusters).
Tesselmax embeds pocketpy as the scripting engine in our simulation console, so user scripts drive
geometry, materials, sources, and post-processing.
Website: [https://fdtd.io](https://fdtd.io)
## Upgrade to v2.0
pkpy v2.0 is a C11 project instead of C++17. All your existing code for v1.x won't work anymore.

View File

@ -3,7 +3,7 @@ output: .retype
url: https://pocketpy.dev
branding:
title: pocketpy
label: v2.1.9
label: v2.2.0
logo: "./static/logo.png"
favicon: "./static/logo.png"
meta:

View File

@ -1,10 +1,10 @@
#pragma once
// clang-format off
#define PK_VERSION "2.1.9"
#define PK_VERSION "2.2.0"
#define PK_VERSION_MAJOR 2
#define PK_VERSION_MINOR 1
#define PK_VERSION_PATCH 9
#define PK_VERSION_MINOR 2
#define PK_VERSION_PATCH 0
/*************** feature settings ***************/
#ifndef PK_ENABLE_OS // can be overridden by cmake

View File

@ -8,6 +8,9 @@
#include "pocketpy/export.h"
#include "pocketpy/vmath.h"
// for sandbox branch
#include "pocketpy/sandbox.h"
#ifdef __cplusplus
extern "C" {
#endif

View File

@ -0,0 +1,25 @@
#pragma once
#include "pocketpy/export.h"
#include <stdbool.h>
// Fine-grained capabilities of the VM for sandboxing.
typedef struct py_Capabilities {
// FileIO
bool (*file_open)(const char* path, const char* mode);
// os
bool (*os_chdir)(const char* path);
bool (*os_getcwd)();
bool (*os_system)(const char* command);
bool (*os_remove)(const char* path);
// stdc
bool stdc_write; // memset, write_bytes, ...
bool stdc_read; // memcmp, read_bytes, ...
bool stdc_malloc; // malloc
bool stdc_free; // free
} py_Capabilities;
/// Setup the capabilities for the current VM.
PK_API py_Capabilities* py_capabilities();
/// Raise a `KeyboardInterrupt` to interrupt the current execution.
PK_API void py_interrupt();

View File

@ -27,6 +27,7 @@ OPCODE(LOAD_NAME)
OPCODE(LOAD_NONLOCAL)
OPCODE(LOAD_GLOBAL)
OPCODE(LOAD_ATTR)
OPCODE(LOAD_SELF_ATTR)
OPCODE(LOAD_CLASS_GLOBAL)
OPCODE(LOAD_METHOD)
OPCODE(LOAD_SUBSCR)
@ -35,6 +36,7 @@ OPCODE(STORE_FAST)
OPCODE(STORE_NAME)
OPCODE(STORE_GLOBAL)
OPCODE(STORE_ATTR)
OPCODE(STORE_SELF_ATTR)
OPCODE(STORE_SUBSCR)
OPCODE(DELETE_FAST)

View File

@ -35,7 +35,7 @@ A new Flutter FFI plugin project.
s.prepare_command = <<-CMD
rm -rf pocketpy
git clone --branch v2.1.9 --depth 1 https://github.com/pocketpy/pocketpy.git
git clone --branch v2.2.0 --depth 1 https://github.com/pocketpy/pocketpy.git
cd pocketpy
git submodule update --init --recursive --depth 1
bash build_ios_libs.sh

View File

@ -32,7 +32,7 @@ A new Flutter FFI plugin project.
s.prepare_command = <<-CMD
rm -rf pocketpy
git clone --branch v2.1.9 --depth 1 https://github.com/pocketpy/pocketpy.git
git clone --branch v2.2.0 --depth 1 https://github.com/pocketpy/pocketpy.git
cd pocketpy
git submodule update --init --recursive --depth 1
bash build_darwin_libs.sh

View File

@ -1,6 +1,6 @@
name: pocketpy
description: A lightweight Python interpreter for game engines. It supports Android/iOS/Windows/Linux/MacOS.
version: 2.1.9
version: 2.2.0
homepage: https://pocketpy.dev
repository: https://github.com/pocketpy/pocketpy

View File

@ -20,7 +20,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.9
GIT_TAG v2.2.0
)
FetchContent_MakeAvailable(pocketpy)

View File

@ -384,7 +384,7 @@ static bool int__abs__(int argc, py_Ref argv) {
static bool float__abs__(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
py_f64 val = py_tofloat(&argv[0]);
py_newfloat(py_retval(), val < 0 ? -val : val);
py_newfloat(py_retval(), dmath_fabs(val));
return true;
}

View File

@ -447,6 +447,12 @@ static bool str_zfill(int argc, py_Ref argv) {
}
c11_sbuf buf;
c11_sbuf__ctor(&buf);
// a leading sign is kept in front; the padding goes after it
if(self.size > 0 && (self.data[0] == '+' || self.data[0] == '-')) {
c11_sbuf__write_char(&buf, self.data[0]);
self.data++;
self.size--;
}
for(int i = 0; i < delta; i++) {
c11_sbuf__write_char(&buf, '0');
}

View File

@ -701,8 +701,11 @@ double dmath_copysign(double x, double y) {
return ux.f;
}
// https://github.com/kraj/musl/blob/kraj/master/src/math/fabs.c
double dmath_fabs(double x) {
return (x < 0) ? -x : x;
union Float64Bits u = { .f = x };
u.i &= -1ULL/2;
return u.f;
}
double dmath_ceil(double x) {

View File

@ -60,6 +60,7 @@ typedef struct Expr {
typedef struct Ctx {
CodeObject* co; // 1 CodeEmitContext <=> 1 CodeObject*
FuncDecl* func; // optional, weakref
py_Name n_self;
int level;
int curr_iblock;
bool is_compiling_class;
@ -70,7 +71,7 @@ typedef struct Ctx {
typedef struct Expr Expr;
static void Ctx__ctor(Ctx* self, CodeObject* co, FuncDecl* func, int level);
static void Ctx__ctor(Ctx* self, CodeObject* co, FuncDecl* func, int level, py_Name n_self);
static void Ctx__dtor(Ctx* self);
static int Ctx__prepare_loop_divert(Ctx* self, int line, bool is_break);
static int Ctx__enter_block(Ctx* self, CodeBlockType type);
@ -732,9 +733,9 @@ static void NamedExpr__dtor(Expr* self_) {
static void NamedExpr__emit_(Expr* self_, Ctx* ctx) {
NamedExpr* self = (NamedExpr*)self_;
vtemit_(self->rhs, ctx); // [value]
Ctx__emit_(ctx, OP_DUP_TOP, BC_NOARG, self->line); // [value, value]
vtemit_store((Expr*)self->name, ctx); // [value]
vtemit_(self->rhs, ctx); // [value]
Ctx__emit_(ctx, OP_DUP_TOP, BC_NOARG, self->line); // [value, value]
vtemit_store((Expr*)self->name, ctx); // [value]
}
static NamedExpr* NamedExpr__new(int line, NameExpr* name, Expr* rhs) {
@ -997,8 +998,23 @@ void AttribExpr__dtor(Expr* self_) {
vtdelete(self->child);
}
static bool is_self_xxx(Expr* child, Ctx* ctx) {
if(child->vt->is_name) {
NameExpr* ne = (NameExpr*)child;
if(ne->scope == NAME_LOCAL && ne->name == ctx->n_self) {
int index = c11_smallmap_n2d__get(&ctx->co->varnames_inv, ne->name, -1);
if(index == 0) return true;
}
}
return false;
}
void AttribExpr__emit_(Expr* self_, Ctx* ctx) {
AttribExpr* self = (AttribExpr*)self_;
if(is_self_xxx(self->child, ctx)) {
Ctx__emit_(ctx, OP_LOAD_SELF_ATTR, Ctx__add_name(ctx, self->name), self->line);
return;
}
vtemit_(self->child, ctx);
Ctx__emit_(ctx, OP_LOAD_ATTR, Ctx__add_name(ctx, self->name), self->line);
}
@ -1012,6 +1028,10 @@ bool AttribExpr__emit_del(Expr* self_, Ctx* ctx) {
bool AttribExpr__emit_store(Expr* self_, Ctx* ctx) {
AttribExpr* self = (AttribExpr*)self_;
if(is_self_xxx(self->child, ctx)) {
Ctx__emit_(ctx, OP_STORE_SELF_ATTR, Ctx__add_name(ctx, self->name), self->line);
return true;
}
vtemit_(self->child, ctx);
Ctx__emit_(ctx, OP_STORE_ATTR, Ctx__add_name(ctx, self->name), self->line);
return true;
@ -1127,9 +1147,10 @@ CallExpr* CallExpr__new(int line, Expr* callable) {
}
/* context.c */
static void Ctx__ctor(Ctx* self, CodeObject* co, FuncDecl* func, int level) {
static void Ctx__ctor(Ctx* self, CodeObject* co, FuncDecl* func, int level, py_Name n_self) {
self->co = co;
self->func = func;
self->n_self = n_self;
self->level = level;
self->curr_iblock = 0;
self->is_compiling_class = false;
@ -1361,6 +1382,7 @@ typedef struct Compiler {
Token* tokens;
int tokens_length;
py_Name n_self;
int i; // current token index
c11_vector /*T=CodeEmitContext*/ contexts;
@ -1370,6 +1392,7 @@ static void Compiler__ctor(Compiler* self, SourceData_ src, Token* tokens, int t
self->src = src;
self->tokens = tokens;
self->tokens_length = tokens_length;
self->n_self = py_name("self");
self->i = 0;
c11_vector__ctor(&self->contexts, sizeof(Ctx));
}
@ -1540,7 +1563,7 @@ static Error* EXPR_VARS(Compiler* self) {
static void push_global_context(Compiler* self, CodeObject* co) {
co->start_line = self->i == 0 ? 1 : prev()->line;
Ctx* ctx = c11_vector__emplace(&self->contexts);
Ctx__ctor(ctx, co, NULL, self->contexts.length);
Ctx__ctor(ctx, co, NULL, self->contexts.length, self->n_self);
}
static Error* pop_context(Compiler* self) {
@ -1715,9 +1738,7 @@ static Error* exprWalrus(Compiler* self) {
int line = prev()->line;
// LHS is on the stack; verify it's a simple name
Expr* lhs = Ctx__s_top(ctx());
if(!lhs->vt->is_name) {
return SyntaxError(self, "':=' target must be a simple name");
}
if(!lhs->vt->is_name) { return SyntaxError(self, "':=' target must be a simple name"); }
check(parse_expression(self, PREC_NAMED_EXPR + 1, false));
Expr* rhs = Ctx__s_popx(ctx());
NameExpr* name = (NameExpr*)Ctx__s_popx(ctx());
@ -1940,7 +1961,7 @@ static Error* exprCall(Compiler* self) {
Error* err;
Expr* callable = Ctx__s_popx(ctx());
int line = prev()->line;
CallExpr* e = CallExpr__new(line, callable);
Ctx__s_push(ctx(), (Expr*)e); // push onto the stack in advance
do {
@ -2289,7 +2310,7 @@ static FuncDecl_ push_f_context(Compiler* self, c11_sv name, int* out_index) {
*out_index = top_ctx->co->func_decls.length - 1;
// push new context
top_ctx = c11_vector__emplace(&self->contexts);
Ctx__ctor(top_ctx, &decl->code, decl, self->contexts.length);
Ctx__ctor(top_ctx, &decl->code, decl, self->contexts.length, self->n_self);
return decl;
}

View File

@ -325,6 +325,23 @@ __NEXT_STEP:
}
DISPATCH();
}
case OP_LOAD_SELF_ATTR: {
assert(!frame->is_locals_special);
py_Ref val = &frame->locals[0];
if(!py_isnil(val)) {
// LOAD_ATTR
py_Name name = co_names[byte.arg];
if(py_getattr(val, name)) {
PUSH(py_retval());
} else {
goto __ERROR;
}
DISPATCH();
}
py_Name name = c11__getitem(py_Name, &frame->co->varnames, byte.arg);
UnboundLocalError(name);
goto __ERROR;
}
case OP_LOAD_CLASS_GLOBAL: {
assert(self->curr_class);
py_Name name = co_names[byte.arg];
@ -427,6 +444,20 @@ __NEXT_STEP:
STACK_SHRINK(2);
DISPATCH();
}
case OP_STORE_SELF_ATTR: {
assert(!frame->is_locals_special);
py_Ref val = &frame->locals[0];
if(!py_isnil(val)) {
// [val, a] -> a.b = val
py_Name name = co_names[byte.arg];
if(!py_setattr(val, name, TOP())) goto __ERROR;
POP();
DISPATCH();
}
py_Name name = c11__getitem(py_Name, &frame->co->varnames, byte.arg);
UnboundLocalError(name);
goto __ERROR;
}
case OP_STORE_SUBSCR: {
// [val, a, b] -> a[b] = val
py_Ref magic = py_tpfindmagic(SECOND()->type, __setitem__);

View File

@ -77,8 +77,10 @@ static bool disassemble(CodeObject* co) {
case OP_LOAD_NONLOCAL:
case OP_STORE_GLOBAL:
case OP_LOAD_ATTR:
case OP_LOAD_SELF_ATTR:
case OP_LOAD_METHOD:
case OP_STORE_ATTR:
case OP_STORE_SELF_ATTR:
case OP_DELETE_ATTR:
case OP_BEGIN_CLASS:
case OP_DELETE_GLOBAL:

View File

@ -5,8 +5,8 @@
// Magic number for CodeObject serialization: "CO" = 0x434F
#define CODEOBJECT_MAGIC 0x434F
#define CODEOBJECT_VER_MAJOR 1
#define CODEOBJECT_VER_MINOR 0
#define CODEOBJECT_VER_MINOR_MIN 0
#define CODEOBJECT_VER_MINOR 1
#define CODEOBJECT_VER_MINOR_MIN 1
// Forward declarations
static void FuncDecl__serialize(c11_serializer* s,

View File

@ -97,6 +97,9 @@ assert 3.4e+3 == 3400.0
assert abs(1.0) == 1.0
assert abs(-1.0) == 1.0
assert abs(0.0) == 0.0
# abs(-0.0) is 0.0, not -0.0. `==` cannot tell them apart, so check the sign.
assert str(abs(-0.0)) == '0.0'
assert str(abs(0.0)) == '0.0'
# import math
# assert math.isnan(0/0)

View File

@ -112,6 +112,9 @@ assert s2.join( seq ) == "runoob"
assert 'x'.zfill(5) == '0000x'
assert '568'.zfill(1) == '568'
assert '-5'.zfill(4) == '-005'
assert '+5'.zfill(4) == '+005'
assert '-'.zfill(3) == '-00'
num = 6
assert str(num) == '6'