Compare commits

...

42 Commits

Author SHA1 Message Date
BLUELOVETH
981e6e6e88 support abs() for complex objects 2024-07-21 08:29:53 +00:00
BLUELOVETH
066554f241
Update website.yml 2024-07-13 17:02:08 +08:00
BLUELOVETH
9bb19f4c9c
Update .gitignore 2024-07-13 16:39:51 +08:00
BLUELOVETH
6134175df4
Update config.h 2024-07-13 16:39:15 +08:00
BLUELOVETH
51ce483c48
Merge pull request #290 from 16bit-ykiko/fix-capsule
some fix.
2024-07-02 23:21:38 +08:00
ykiko
f803a7eca6 some fix. 2024-07-02 23:14:44 +08:00
BLUELOVETH
8d0e304ecd
Merge pull request #289 from 16bit-ykiko/fix-capsule
update capsule.
2024-07-02 22:57:10 +08:00
ykiko
901cf714a2 update. 2024-07-02 22:38:37 +08:00
BLUELOVETH
37c22653f1
Merge pull request #286 from 16bit-ykiko/pybind11-improvement
some fix.
2024-06-20 12:08:28 +08:00
ykiko
f162cd308a some fix. 2024-06-20 12:03:45 +08:00
BLUELOVETH
4ba0b7d2e2
Update pybind11.md 2024-06-19 16:19:20 +08:00
BLUELOVETH
2d6def4bbd
Merge pull request #285 from 16bit-ykiko/pybind11-doc
update document.
2024-06-19 16:10:29 +08:00
ykiko
a5a3c75b83 fix typo. 2024-06-19 16:09:54 +08:00
ykiko
8ffcacf99f Update. 2024-06-19 16:06:16 +08:00
ykiko
a2cb91b125 update document. 2024-06-19 15:51:15 +08:00
BLUELOVETH
86006d0b61
Update migrate.md 2024-06-19 15:51:01 +08:00
BLUELOVETH
a08c5af6e9
Update migrate.md 2024-06-19 15:47:41 +08:00
BLUELOVETH
475be8ce80
Merge pull request #283 from 16bit-ykiko/doc
Update README.
2024-06-19 14:24:35 +08:00
ykiko
12d748ba7d fix format. 2024-06-19 14:15:48 +08:00
BLUELOVETH
0c1bd532f8
Merge pull request #282 from 16bit-ykiko/pybind11-fix
some fix.
2024-06-19 13:05:58 +08:00
ykiko
d52cbec80c Update README. 2024-06-19 12:32:58 +08:00
ykiko
e16aa501ef some fix. 2024-06-19 12:19:05 +08:00
blueloveTH
59bbecdbee fix versions 2024-06-19 11:06:19 +08:00
blueloveTH
f5c356a047 some fix 2024-06-19 11:04:32 +08:00
blueloveTH
68f8cd1159 Update migrate.md 2024-06-19 11:02:56 +08:00
blueloveTH
259394d7ad doc fix 2024-06-19 10:58:13 +08:00
BLUELOVETH
fece79b169
Merge pull request #280 from 16bit-ykiko/pybind11
experiment implementation of pybind11.
2024-06-18 21:06:22 +08:00
ykiko
95553438e1 experiment implementation of pybind11. 2024-06-18 20:51:28 +08:00
BLUELOVETH
a9d296dedc
Merge pull request #278 from 16bit-ykiko/fix_exec
fix py_exec and py_eval.
2024-06-18 20:46:15 +08:00
ykiko
21d886f1a4 fix format. 2024-06-18 20:39:02 +08:00
ykiko
435ded95b5 fix format. 2024-06-18 20:37:57 +08:00
ykiko
f937fc1da8 fix format. 2024-06-18 20:37:27 +08:00
ykiko
71432a4054 fix format. 2024-06-18 20:36:42 +08:00
ykiko
cc4dd34c86 fix py_exec and py_eval. 2024-06-18 19:49:30 +08:00
BLUELOVETH
6736fa7f50
fix bind -1 2024-06-17 20:04:49 +08:00
BLUELOVETH
0406dfab78
Delete docs/1_5_0.md 2024-06-09 22:49:28 +08:00
Janos
f9c00fd706
Fixes #248: sys.stderr.write() does not use vm->stderr_write (#249)
Signed-off-by: Janos Bonic <86970079+janosdebugs@users.noreply.github.com>
2024-05-30 00:17:36 +08:00
Vadim Grigoruk
56369bfa6f
wasm build fix (#245)
Thanks for your fix.
2024-05-27 13:32:07 +08:00
blueloveTH
02b922a93e add vec test v1.0 2024-05-25 14:51:33 +08:00
blueloveTH
68c8f72ba5 Create vec.py 2024-05-25 14:47:10 +08:00
blueloveTH
845c9153e8 add version macro for v1 2024-05-19 20:26:53 +08:00
blueloveTH
77e3d2e88f some fix 2024-05-14 00:10:40 +08:00
36 changed files with 3421 additions and 596 deletions

View File

@ -26,7 +26,6 @@ jobs:
- name: Setup emsdk
uses: mymindstorm/setup-emsdk@v12
with:
version: 3.1.25
actions-cache-folder: 'emsdk-cache'
- name: Compile
run: |

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ __pycache__/
.DS_Store
.coverage
.idea
.cache
gmon.out
gprof.txt

View File

@ -25,7 +25,8 @@ pkpy is extremely easy to embed via a single header file `pocketpy.h`, without e
Please see https://pocketpy.dev for details and try the following resources.
+ [Live Python Demo](https://pocketpy.dev/static/web/): Python REPL of the latest version
+ [Live C++ Examples](https://pocketpy.github.io/examples/): Common usage of pkpy in C++
> We are moving to v2.0 branch, which is a complete refactor of this project in C11. See [Migration Guide](https://pocketpy.dev/migrate/) for more details.
## Supported Platforms
@ -90,39 +91,33 @@ python scripts/run_tests.py
### Example
```cpp
#include "pocketpy.h"
#include <pybind11/pybind11.h>
using namespace pkpy;
namespace py = pybind11;
int main() {
// Create a virtual machine
VM* vm = new VM();
// Start the interpreter
py::scoped_interpreter guard{};
// Hello world!
vm->exec("print('Hello world!')");
py::exec("print('Hello world!')");
// Create a list
vm->exec("a = [1, 2, 3]");
py::exec("a = [1, 2, 3]");
// Eval the sum of the list
PyVar result = vm->eval("sum(a)");
std::cout << "Sum of the list: "<< py_cast<int>(vm, result) << std::endl; // 6
auto result = py::eval("sum(a)");
std::cout << "Sum of the list: " << result.cast<int>() << std::endl; // 6
// Bindings
vm->bind(vm->_main, "add(a: int, b: int)",
[](VM* vm, ArgsView args){
int a = py_cast<int>(vm, args[0]);
int b = py_cast<int>(vm, args[1]);
return py_var(vm, a + b);
auto m = py::module_::__main__();
m.def("add", [](int a, int b) {
return a + b;
});
// Call the function
PyVar f_add = vm->_main->attr("add");
result = vm->call(f_add, py_var(vm, 3), py_var(vm, 7));
std::cout << "Sum of 2 variables: "<< py_cast<int>(vm, result) << std::endl; // 10
std::cout << "Sum of 2 variables: " << m.attr("add")(1, 2).cast<int>() << std::endl; // 10
// Dispose the virtual machine
delete vm;
return 0;
}
```

23
benchmarks/vec.py Normal file
View File

@ -0,0 +1,23 @@
import sys
is_cpython = hasattr(sys, 'getrefcount')
if is_cpython:
class vec2:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return vec2(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
else:
from linalg import vec2
x = vec2(0, 0)
for i in range(10000000):
x += vec2(1, 1)
assert x == vec2(10000000, 10000000)

View File

@ -1,218 +0,0 @@
---
icon: log
title: 'Upgrade to v1.5.0'
order: 25
---
We are applying a major API refactoring in this release. The main goal is to make the API more consistent and easier to use. We are also adding new features and improvements. This release is not backward compatible with the previous versions. Please read the following guide to upgrade your project.
## Alias for `PyObject*`
We are going to make a very big change in `v2.0` which may break everything!
In order to make this upgrade easier, we are introducing an alias for `PyObject*` in `v1.5.0`.
```cpp
using PyVar = PyObject*;
```
Please replace all `PyObject*` with `PyVar` in your code in order to be compatible with `v2.0` in the future.
We will try our best to keep the compatibility with `v1.x` series.
## Old style bindings
We introduced the new style bindings `vm->bind` in [`v1.1.3`](https://github.com/pocketpy/pocketpy/releases/tag/v1.1.3) and deprecated the old style bindings `vm->bind_func<>` and `vm->bind_method<>`.
In this release, we added an ARGC-based binding `vm->bind_func` (without template parameter)
to replace the old style bindings. If you are still using `vm->bind_func<>` and `vm->bind_method<>`,
please update your code to use `vm->bind_func` instead.
```cpp
// old style (removed)
vm->bind_func<N>(obj, "add", f_add);
vm->bind_method<M>(obj, "get", f_get);
// new ARGC-based binding
vm->bind_func(obj, "add", N, f_add);
vm->bind_func(obj, "get", M+1, f_get); // +1 for `self`
```
## Class bindings
Previously, if we want to write bindings for a C++ class, we need to add `PY_CLASS` macro and implement a magic method `_register`. This way was known as an intrusive way. For example:
```cpp
struct Point{
PY_CLASS(Point, test, Point)
int x, y;
static void _register(VM* vm, PyVar mod, PyVar type){
// do bindings here
}
};
int main(){
VM* vm = new VM();
PyVar mod = vm->new_module("test");
Point::register_class(vm, mod);
return 0;
}
```
In `v1.5.0`, the `PY_CLASS` macro was deprecated. We introduced a non-intrusive way to write class bindings via `vm->register_user_class<>`. The example above can be rewritten as follows:
```cpp
struct Point{
int x, y;
};
int main(){
VM* vm = new VM();
PyVar mod = vm->new_module("test");
vm->register_user_class<Point>(mod, "Point",
[](VM* vm, PyVar mod, PyVar type){
// do bindings here
});
return 0;
}
```
The magic method `_register` is kept for users who still wants to use the intrusive way.
This is achieved by an overloaded version of `vm->register_user_class<>`. For example:
```cpp
struct Point{
int x, y;
static void _register(VM* vm, PyVar mod, PyVar type){
// do bindings here (if you like the intrusive way)
}
};
int main(){
VM* vm = new VM();
PyVar mod = vm->new_module("test");
vm->register_user_class<Point>(mod, "Point");
return 0;
}
```
## Signature of `_import_handler`
The signature of `_import_handler` was changed from:
```cpp
unsigned char* (*)(const char* name_p, int name_size, int* out_size);
```
to:
```cpp
unsigned char* (*)(const char* name, int* out_size);
```
This is because `str` object was ensured to be null-terminated after `v1.4.1`.
## Signature of `bind__next__`
`vm->bind__next__` is a special method that is used to implement the iterator protocol.
Previously, if you want to return multiple values, you need to pack them into a tuple.
For example:
```cpp
vm->bind__next__(type, [](VM* vm, PyVar _0){
if(is_end) return vm->StopIteration;
// ...
PyVar a = VAR(1);
PyVar b = VAR(2);
return VAR(Tuple(a, b));
});
```
```python
for a, b in my_iter:
# There is tuple creation and destruction for each iteration
...
```
It is not efficient because unnecessary tuple creation and destruction are involved.
In `v1.5.0`, you need to use stack-based style to reimplement the above code:
```cpp
vm->bind__next__(type, [](VM* vm, PyVar _0) -> unsigned{
if(is_end) return 0; // return 0 indicates StopIteration
// ...
PyVar a = VAR(1);
PyVar b = VAR(2);
vm->s_data.push(a); // directly push to the stack
vm->s_data.push(b); // directly push to the stack
return 2; // return the number of values
});
```
In this way, the interpreter only creates a tuple when it is necessary.
It can improve ~25% performance when iterating over a large array.
```python
for a, b in my_iter:
# No tuple creation and destruction
...
for t in my_iter:
# Create a tuple lazily
...
```
## Builtin function `next()`
Previously, `next()` returns `StopIteration` when the iterator is exhausted.
In `v1.5.0`, it raises `StopIteration` exception instead, which is consistent with CPython.
If you like the old way, you can use the following snippet to import the old `next()` function:
```python
from __builtins import next
```
Related C++ APIs do not change. They still return `vm->StopIteration` to indicate the end of iteration.
## User config support
We used to read `user_config.h` file to override the default configurations.
In `v1.5.0`, this is no longer supported.
Please use config macros before `#include "pocketpy.h"` directly.
```cpp
#define PK_ENABLE_OS 1
#define PK_ENABLE_THREAD 1
#define PK_ENABLE_PROFILER 1
// for all config macros, please refer to `include/pocketpy/config.h`
#include "pocketpy.h"
```
## Debugger and profiler
We added a macro `PK_ENABLE_PROFILER` (default is 0) to control the availability of the builtin debugger and profiler.
If you want to use them, for example, you want to call `breakpoint()` or `import line_profiler`, you need to set `PK_ENABLE_PROFILER` to 1 before `#include "pocketpy.h"`.
```cpp
#define PK_ENABLE_PROFILER 1
#include "pocketpy.h"
```
!!!
Enabling the profiler has a performance overhead. Only enable it when you need it.
!!!
## Type checking functions
+ `vm->is_non_tagged_type` was removed. Use `vm->is_type` instead.
+ `vm->check_non_tagged_type` was removed. Use `vm->check_type` instead.
## Python Stringify functions
The following functions now return `Str` object instead of `PyVar`:
+ `vm->py_str`
+ `vm->py_repr`
+ `vm->py_json`
Also, `vm->bind__str__` and `vm->bind__repr__` were changed to return `Str` object.

View File

@ -1,333 +0,0 @@
---
icon: log
title: 'Cheatsheet'
order: 22
---
## Basics
Setup pocketpy
```cpp
#include "pocketpy.h"
using namespace pkpy;
```
Create a python virtual machine
```cpp
VM* vm = new VM();
```
Dispose a python virtual machine
```cpp
delete vm;
```
Execute a source string
```cpp
vm->exec("print('Hello!')");
```
Evaluate a source string
```cpp
PyVar obj = vm->eval("123");
std::cout << py_cast<int>(vm, obj); // 123
```
Compile a source string into a code object
```cpp
CodeObject_ co = vm->compile("print('Hello!')", "main.py", EXEC_MODE);
```
Execute a compiled code object
```cpp
try{
vm->_exec(co); // may throw
}catch(Exception& e){
std::cerr << e.summary() << std::endl;
}
```
## Interop with native types
Create primitive objects
```cpp
PyVar obj;
obj = py_var(vm, 1); // create a int
obj = py_var(vm, 1.0); // create a float
obj = py_var(vm, "123"); // create a string
obj = py_var(vm, true); // create a bool
```
Create a tuple object
```cpp
// obj = (1, 1.0, '123')
Tuple t(3);
t[0] = py_var(vm, 1);
t[1] = py_var(vm, 1.0);
t[2] = py_var(vm, "123");
PyVar obj = py_var(vm, std::move(t));
```
Create a list object
```cpp
// obj = [1, 1.0, '123']
List t;
t.push_back(py_var(vm, 1));
t.push_back(py_var(vm, 1.0));
t.push_back(py_var(vm, "123"));
PyVar obj = py_var(vm, std::move(t));
```
Create a dict object
```cpp
// obj = {'x': 1, 'y': '123'}
Dict d(vm);
d.set(py_var(vm, "x"), py_var(vm, 1));
d.set(py_var(vm, "y"), py_var(vm, "123"));
PyVar obj = py_var(vm, std::move(d));
```
Get native types from python objects
```cpp
PyVar obj;
i64 a = py_cast<i64>(vm, obj);
f64 b = py_cast<f64>(vm, obj);
Str& c = py_cast<Str&>(vm, obj); // reference cast
bool d = py_cast<bool>(vm, obj);
Tuple& e = py_cast<Tuple&>(vm, obj); // reference cast
List& f = py_cast<List&>(vm, obj); // reference cast
Dict& g = py_cast<Dict&>(vm, obj); // reference cast
```
Get native types without type checking
```cpp
// unsafe version 1 (for int object you must use `_py_cast`)
i64 a = _py_cast<i64>(vm, obj);
f64 b = _py_cast<f64>(vm, obj);
Tuple& c = _py_cast<Tuple&>(vm, obj);
// unsafe version 2 (for others, you can also use `PK_OBJ_GET` macro)
Str& a_ = PK_OBJ_GET(Str, obj);
List& b_ = PK_OBJ_GET(List, obj);
Tuple& c_ = PK_OBJ_GET(Tuple, obj);
```
## Access python types
Access built-in python types
```cpp
PyVar int_t = vm->_t(VM::tp_int);
PyVar float_t = vm->_t(VM::tp_float);
PyVar object_t = vm->_t(VM::tp_object);
PyVar tuple_t = vm->_t(VM::tp_tuple);
PyVar list_t = vm->_t(VM::tp_list);
```
Access user registered types
```cpp
Type voidp_t = vm->_tp_user<VoidP>();
```
Check if an object is a python type
```cpp
PyVar obj;
bool ok = is_type(obj, VM::tp_int); // check if obj is an int
```
Get the type of a python object
```cpp
PyVar obj = py_var(vm, 1);
PyVar t = vm->_t(obj); // <class 'int'>
Type type = vm->_tp(obj); // VM::tp_int
```
Convert a type object into a type index
```cpp
PyVar int_t = vm->_t(VM::tp_int);
Type t = PK_OBJ_GET(Type, int_t);
// t == VM::tp_int
```
## Access attributes
Check an object supports attribute access
```cpp
PyVar obj;
bool ok = !is_tagged(obj) && obj->is_attr_valid();
```
```python
class MyClass:
def __init__(self, x, y):
self.x = x
self.y = y
def sum(self):
return self.x + self.y
```
Get and set attributes
```cpp
PyVar obj = vm->exec("MyClass(1, 2)");
PyVar x = vm->getattr(obj, "x"); // obj.x
vm->setattr(obj, "x", py_var(vm, 3)); // obj.x = 3
```
## Call python functions
```python
def add(a, b):
return a + b
```
Call a function
```cpp
PyVar f_add = vm->eval("add");
PyVar ret = vm->call(f_add, py_var(vm, 1), py_var(vm, 2));
std::cout << py_cast<int>(vm, ret); // 3
```
Call a method
```cpp
PyVar obj = vm->exec("MyClass(1, 2)");
PyVar ret = vm->call_method(obj, "sum");
std::cout << CAST(i64, ret); // 3
```
Cache the name of a function or method to avoid string-based lookup
```cpp
// cache the name "add" to avoid string-based lookup
const static StrName m_sum("sum");
PyVar ret = vm->call_method(obj, m_sum);
```
## Special operations
Compare two python objects
```cpp
PyVar obj1 = py_var(vm, 1);
PyVar obj2 = py_var(vm, 2);
bool ok = vm->py_eq(obj1, obj2);
```
Convert a python object to string
```cpp
PyVar obj = py_var(vm, 123);
std::cout << vm->py_str(obj); // 123
```
Get the string representation of a python object
```cpp
PyVar obj = py_var(vm, "123");
std::cout << vm->py_repr(obj); // "'123'"
```
Get the JSON representation of a python object
```cpp
PyVar obj = py_var(vm, 123);
std::cout << vm->py_json(obj); // "123"
```
Get the hash value of a python object
```cpp
PyVar obj = py_var(vm, 1);
i64 h = vm->py_hash(obj); // 1
```
Get the iterator of a python object
```cpp
PyVar obj = vm->eval("range(3)");
PyVar iter = vm->py_iter(obj);
```
Get the next item of an iterator
```cpp
PyVar obj = vm->py_next(iter);
if(obj == vm->StopIteration){
// end of iteration
}else{
// process obj
}
```
Convert a python iterable to a list
```cpp
PyVar obj = vm->eval("range(3)");
List list = vm->py_list(obj);
```
## Bindings
Bind a native function
```cpp
vm->bind(obj, "add(a: int, b: int) -> int", [](VM* vm, ArgsView args){
int a = py_cast<int>(vm, args[0]);
int b = py_cast<int>(vm, args[1]);
return py_var(vm, a + b);
});
```
Bind a property
```cpp
// getter and setter of property `x`
vm->bind_property(type, "x: int",
[](VM* vm, ArgsView args){
Point& self = PK_OBJ_GET(Point, args[0]);
return VAR(self.x);
},
[](VM* vm, ArgsView args){
Point& self = PK_OBJ_GET(Point, args[0]);
self.x = py_cast<int>(vm, args[1]);
return vm->None;
});
```
## Modules
Create a source module
```cpp
vm->_lazy_modules["test"] = "pi = 3.14";
// import test
// print(test.pi) # 3.14
```
Create a native module
```cpp
PyVar mod = vm->new_module("test");
vm->setattr(mod, "pi", py_var(vm, 3.14));
```

View File

@ -5,6 +5,12 @@ label: Welcome
# Welcome to pocketpy
!!!
We are moving to v2.0 branch, which is a complete refactor of this project in C11.
See [Migration Guide](migrate.md) for more details.
!!!
pkpy is a lightweight(~15K LOC) Python interpreter for game scripting, built on C++17 with STL.
It aims to be an alternative to lua for game scripting, with elegant syntax, powerful features and competitive performance.

32
docs/migrate.md Normal file
View File

@ -0,0 +1,32 @@
---
icon: log
title: 'Migration Guide'
order: 22
---
## Overview
v2.0 branch is a complete refactor of pocketpy in C11,
which enables users to run pocketpy on platforms that do not support C++.
Also we redesign the core interpreter to be more efficient and maintainable
by using modern C11 language features.
> v2.0 will be released on 2024/08.
## API compatibility
| name | v1.x | v2.0 |
| --- | --- | --- |
| legacy C++ API (v1.x only) | ✅ | ❌ |
| legacy C API (v1.x only) | ✅ | ❌ |
| C11 API (v2.0 only) | ❌ | ✅ (work-in-progress) |
| pybind11 API (both) | ✅ | ✅ (work-in-progress) |
## Suggestions
- If you are a C++ user
- Use **pybind11 API** if you want to upgrade to v2.0 in the future
- Use **legacy C++ API** if you want to stay in v1.x
- If you are a C user
- Use **v2.0's C11 API** (will be available soon)
- Use **legacy C API** if you want to stay in v1.x

291
docs/pybind11.md Normal file
View File

@ -0,0 +1,291 @@
---
icon: log
title: 'pybind11 User Guide'
order: 21
---
## Quick Start
pkpy provides a [pybind11](https://pybind11.readthedocs.io/en/stable/) compatible layer which allows users to do convenient bindings.
To begin with, use `py::scoped_interpreter guard{}` to start the interpreter before using any Python objects.
Or explicitly call `py::interpreter::initialize()` and `py::interpreter::finalize()`.
### module
```cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_EMBEDDED_MODULE(example, m) {
m.def("add", [](int a, int b) {
return a + b;
});
auto math = m.def_submodule("math");
}
```
### function
```cpp
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
void register_function(py::module_& m)
{
m.def("add", py::overload_cast<int, int>(&add));
// support function overload
m.def("add", py::overload_cast<int, int, int>(&add));
// bind with default arguments
m.def("sub", [](int a, int b) {
return a - b;
}, py::arg("a") = 1, py::arg("b") = 2);
// bind *args
m.def("add", [](py::args args) {
int sum = 0;
for (auto& arg : args) {
sum += arg.cast<int>();
}
return sum;
});
// bind **kwargs
m.def("add", [](py::kwargs kwargs) {
int sum = 0;
for (auto item : kwargs) {
sum += item.second.cast<int>();
}
return sum;
});
}
```
### class
```cpp
struct Point
{
const int x;
int y;
public:
Point() : x(0), y(0) {}
Point(int x, int y) : x(x), y(y) {}
Point(const Point& p) : x(p.x), y(p.y) {}
std::string stringfy() const {
return "(" + std::to_string(x) + ", " + std::to_string(y) + ")";
}
};
struct Point3D : Point
{
private:
int z;
public:
Point3D(int x, int y, int z) : Point(x, y), z(z) {}
int get_z() const { return z; }
void set_z(int z) { this->z = z; }
};
void bind_class(py::module_& m)
{
py::class_<Point>(m, "Point")
.def(py::init<>())
.def(py::init<int, int>())
.def(py::init<const Point&>())
.def_readonly("x", &Point::x)
.def_readwrite("y", &Point::y)
.def("__str__", &Point::stringfy);
// only support single inheritance
py::class_<Point3D, Point>(m, "Point3D", py::dynamic_attr())
.def(py::init<int, int, int>())
.def_property("z", &Point3D::get_z, &Point3D::set_z);
// dynamic_attr will enable the dict of bound class
}
```
### operators
```cpp
#include <pybind11/operators.h>
namespace py = pybind11;
struct Int {
int value;
Int(int value) : value(value) {}
Int operator+(const Int& other) const {
return Int(value + other.value);
}
Int operator-(const Int& other) const {
return Int(value - other.value);
}
bool operator==(const Int& other) const {
return value == other.value;
}
bool operator!=(const Int& other) const {
return value != other.value;
}
};
void bind_operators(py::module_& m)
{
py::class_<Int>(m, "Int")
.def(py::init<int>())
.def(py::self + py::self)
.def(py::self - py::self)
.def(py::self == py::self)
.def(py::self != py::self);
// other operators are similar
}
```
### py::object
`py::object` is just simple wrapper around `PyVar`. It supports some convenient methods to interact with Python objects.
here are some common methods:
```cpp
obj.attr("x"); // access attribute
obj[1]; // access item
obj.is_none(); // same as obj is None in Python
obj.is(obj2); // same as obj is obj2 in Python
// operators
obj + obj2; // same as obj + obj2 in Python
// ...
obj == obj2; // same as obj == obj2 in Python
// ...
obj(...); // same as obj.__call__(...)
py::cast(obj); // cast to Python object
obj.cast<T>; // cast to C++ type
py::type::of(obj); // get type of obj
py::type::of<T>(); // get type of T, if T is registered
```
you can also create some builtin objects with their according wrappers:
```cpp
py::bool_ b = {true};
py::int_ i = {1};
py::float_ f = {1.0};
py::str s = {"hello"};
py::list l = {1, 2, 3};
py::tuple t = {1, 2, 3};
// ...
```
## More Examples
More examples please see the test [folder](https://github.com/pocketpy/gsoc-2024-dev/tree/main/pybind11/tests) in the GSoC repository. All tested features are supported.
## Limits and Comparison
This is a feature list of pybind11 for pocketpy. It lists all completed and pending features. It also lists the features that cannot be implemented in the current version of pocketpy.
### [Function](https://pybind11.readthedocs.io/en/stable/advanced/functions.html)
- [x] Function overloading
- [x] Return value policy
- [x] is_prepend
- [x] `*args` and `**kwargs`
- [ ] Keep-alive
- [ ] Call Guard
- [x] Default arguments
- [ ] Keyword-Only arguments
- [ ] Positional-Only arguments
- [ ] Allow/Prohibiting None arguments
### [Class](https://pybind11.readthedocs.io/en/stable/classes.html)
- [x] Creating bindings for a custom type
- [x] Binding lambda functions
- [x] Dynamic attributes
- [x] Inheritance and automatic downcasting
- [x] Enumerations and internal types
- [ ] Instance and static fields
> Binding static fields may never be implemented in pocketpy because it requires a metaclass, which is a heavy and infrequently used feature.
### [Exceptions](https://pybind11.readthedocs.io/en/stable/advanced/exceptions.html)
Need further discussion.
### [Smart pointers](https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html)
- [ ] std::shared_ptr
- [ ] std::unique_ptr
- [ ] Custom smart pointers
### [Type conversions](https://pybind11.readthedocs.io/en/stable/advanced/cast/index.html)
- [x] Python built-in types
- [x] STL Containers
- [ ] Functional
- [ ] Chrono
### [Python C++ interface](https://pybind11.readthedocs.io/en/stable/advanced/pycpp/object.html)
Need further discussion.
- [x] `object`
- [x] `none`
- [x] `type`
- [x] `bool_`
- [x] `int_`
- [x] `float_`
- [x] `str`
- [ ] `bytes`
- [ ] `bytearray`
- [x] `tuple`
- [x] `list`
- [ ] `set`
- [x] `dict`
- [ ] `slice`
- [x] `iterable`
- [x] `iterator`
- [ ] `function`
- [ ] `buffer`
- [ ] `memoryview`
- [x] `capsule`
### [Miscellaneous](https://pybind11.readthedocs.io/en/stable/advanced/misc.html)
- [ ] Global Interpreter Lock (GIL)
- [ ] Binding sequence data types, iterators, the slicing protocol, etc.
- [x] Convenient operators binding
### Differences between CPython and pocketpy
- only `add`, `sub` and `mul` have corresponding right versions in pocketpy. So if you bind `int() >> py::self`, it will has no effect in pocketpy.
- `__new__` and `__del__` are not supported in pocketpy.
- in-place operators, such as `+=`, `-=`, `*=`, etc., are not supported in pocketpy.
- thre return value of `globals` is immutable in pocketpy.

View File

@ -3,7 +3,7 @@ output: .retype
url: https://pocketpy.dev
branding:
title: pocketpy
label: v1.5.0
label: v1.4.6
logo: "./static/logo.png"
favicon: "./static/logo.png"
meta:
@ -16,10 +16,10 @@ links:
icon: play
link: "https://pocketpy.dev/static/web/"
target: blank
- text: "Live Examples"
icon: play
link: "https://pocketpy.github.io/examples/"
target: blank
# - text: "Live Examples"
# icon: play
# link: "https://pocketpy.github.io/examples/"
# target: blank
- text: "Github"
icon: mark-github
link: https://github.com/blueloveth/pocketpy

View File

@ -20,7 +20,10 @@
#include <typeindex>
#include <initializer_list>
#define PK_VERSION "1.5.0"
#define PK_VERSION "1.4.6"
#define PK_VERSION_MAJOR 1
#define PK_VERSION_MINOR 4
#define PK_VERSION_PATCH 6
#include "config.h"
#include "export.h"

View File

@ -73,6 +73,7 @@
#pragma warning (disable:4100)
#pragma warning (disable:4244)
#pragma warning (disable:4996)
#pragma warning (disable:4018)
#endif
#ifdef _MSC_VER

View File

@ -224,6 +224,7 @@ extern const StrName __package__;
extern const StrName __path__;
extern const StrName __class__;
extern const StrName __missing__;
extern const StrName __abs__;
extern const StrName pk_id_add;
extern const StrName pk_id_set;

View File

@ -290,7 +290,7 @@ namespace pkpy
const auto size = other.size();
const auto capacity = other.capacity();
m_begin = reinterpret_cast<T*>(other.is_small() ? m_buffer : std::malloc(sizeof(T) * capacity));
uninitialized_copy_n(other.begin, size, this->m_begin);
uninitialized_copy_n(other.m_begin, size, this->m_begin);
m_end = m_begin + size;
m_max = m_begin + capacity;
}

2
include/pybind11/embed.h Normal file
View File

@ -0,0 +1,2 @@
#pragma once
#include "pybind11.h"

View File

@ -0,0 +1,2 @@
#pragma once
#include "pybind11.h"

View File

@ -0,0 +1,173 @@
#pragma once
#include "builtins.h"
namespace pybind11 {
// implement iterator methods for interface
template <typename Derived>
inline iterator interface<Derived>::begin() const {
return handle(vm->py_iter(this->ptr()));
}
template <typename Derived>
inline iterator interface<Derived>::end() const {
return iterator::sentinel();
}
template <typename Derived>
inline str interface<Derived>::package() const {
return handle(this->attr(pkpy::__package__));
}
template <typename Derived>
inline str interface<Derived>::name() const {
return handle(this->attr(pkpy::__name__));
}
template <typename Derived>
inline str interface<Derived>::repr() const {
return handle(str(vm->py_repr(this->ptr())));
}
template <typename policy>
class accessor : public interface<accessor<policy>> {
using key_type = typename policy::key_type;
handle m_obj;
mutable handle m_value;
key_type m_key;
friend interface<handle>;
friend interface<accessor<policy>>;
friend tuple;
friend list;
friend dict;
accessor(const handle& obj, key_type key) : m_obj(obj), m_value(), m_key(key) {}
public:
pkpy::PyVar ptr() const {
if(!m_value) { m_value = policy::get(m_obj, m_key); }
return m_value.ptr();
}
template <typename Value>
accessor& operator= (Value&& value) && {
policy::set(m_obj, m_key, std::forward<Value>(value));
return *this;
}
template <typename Value>
accessor& operator= (Value&& value) & {
m_value = std::forward<Value>(value);
return *this;
}
template <typename T>
T cast() const {
return operator handle ().template cast<T>();
}
operator handle () const { return ptr(); }
};
namespace policy {
struct attr {
using key_type = pkpy::StrName;
static handle get(const handle& obj, pkpy::StrName key) { return vm->getattr(obj.ptr(), key); }
static void set(const handle& obj, pkpy::StrName key, const handle& value) {
vm->setattr(obj.ptr(), key, value.ptr());
}
};
struct item {
using key_type = handle;
static handle get(const handle& obj, const handle& key) {
return vm->call(vm->py_op("getitem"), obj.ptr(), key.ptr());
}
static void set(const handle& obj, const handle& key, const handle& value) {
vm->call(vm->py_op("setitem"), obj.ptr(), key.ptr(), value.ptr());
}
};
struct tuple {
using key_type = int;
static handle get(const handle& obj, int key) { return obj._as<pkpy::Tuple>()[key]; }
static void set(const handle& obj, size_t key, const handle& value) { obj._as<pkpy::Tuple>()[key] = value.ptr(); }
};
struct list {
using key_type = int;
static handle get(const handle& obj, size_t key) { return obj._as<pkpy::List>()[key]; }
static void set(const handle& obj, size_t key, const handle& value) { obj._as<pkpy::List>()[key] = value.ptr(); }
};
struct dict {
using key_type = handle;
static handle get(const handle& obj, const handle& key) { return obj.cast<pybind11::dict>().getitem(key); }
static void set(const handle& obj, const handle& key, const handle& value) {
obj.cast<pybind11::dict>().setitem(key, value);
}
};
} // namespace policy
// implement other methods of interface
template <typename Derived>
inline attr_accessor interface<Derived>::attr(pkpy::StrName key) const {
return attr_accessor(this->ptr(), key);
}
template <typename Derived>
inline attr_accessor interface<Derived>::attr(const char* key) const {
return attr_accessor(this->ptr(), pkpy::StrName(key));
}
template <typename Derived>
inline attr_accessor interface<Derived>::attr(const handle& key) const {
return attr_accessor(this->ptr(), pkpy::StrName(key._as<pkpy::Str>()));
}
template <typename Derived>
inline attr_accessor interface<Derived>::doc() const {
return attr_accessor(this->ptr(), pkpy::StrName("__doc__"));
}
template <typename Derived>
inline item_accessor interface<Derived>::operator[] (int index) const {
return item_accessor(this->ptr(), int_(index));
}
template <typename Derived>
inline item_accessor interface<Derived>::operator[] (const char* key) const {
return item_accessor(this->ptr(), str(key));
}
template <typename Derived>
inline item_accessor interface<Derived>::operator[] (const handle& key) const {
return item_accessor(this->ptr(), key);
}
inline tuple_accessor tuple::operator[] (int i) const { return tuple_accessor(this->ptr(), i); }
inline list_accessor list::operator[] (int i) const { return list_accessor(this->ptr(), i); }
inline dict_accessor dict::operator[] (int index) const { return dict_accessor(this->ptr(), int_(index)); }
inline dict_accessor dict::operator[] (std::string_view key) const { return dict_accessor(this->ptr(), str(key)); }
inline dict_accessor dict::operator[] (const handle& key) const { return dict_accessor(this->ptr(), key); }
} // namespace pybind11

View File

@ -0,0 +1,226 @@
#pragma once
#include "types.h"
#include "type_traits.h"
namespace pybind11 {
inline object eval(std::string_view code, const handle& global = none{}, handle local = none{}) {
return vm->py_eval(code, global.ptr(), local.ptr());
}
inline void exec(std::string_view code, const handle& global = none{}, handle local = none{}) {
vm->py_exec(code, global.ptr(), local.ptr());
}
/// globas() in pkpy is immutable, your changes will not be reflected in the Python interpreter
inline dict globals() {
auto& proxy = eval("globals()")._as<pkpy::MappingProxy>().attr();
dict result;
#if PK_VERSION_MAJOR == 2
proxy.apply(
[](pkpy::StrName key, pkpy::PyVar value, void* data) {
auto& dict = static_cast<pybind11::dict*>(data)->_as<pkpy::Dict>();
auto key_ = pybind11::str(key.sv()).ptr();
dict.set(vm, key_, value);
},
&result);
#else
proxy.apply([&](pkpy::StrName key, pkpy::PyVar value) {
result.setitem(str(key.sv()), value);
});
#endif
return result;
}
// wrapper for builtin functions in Python
inline bool hasattr(const handle& obj, const handle& name) {
return vm->getattr(obj.ptr(), name._as<pkpy::Str>(), false) != nullptr;
}
inline bool hasattr(const handle& obj, const char* name) { return vm->getattr(obj.ptr(), name, false) != nullptr; }
inline void delattr(const handle& obj, const handle& name) { vm->delattr(obj.ptr(), name._as<pkpy::Str>()); }
inline void delattr(const handle& obj, const char* name) { vm->delattr(obj.ptr(), name); }
inline object getattr(const handle& obj, const handle& name) { return vm->getattr(obj.ptr(), name._as<pkpy::Str>()); }
inline object getattr(const handle& obj, const char* name) { return vm->getattr(obj.ptr(), name); }
inline object getattr(const handle& obj, const handle& name, const handle& default_) {
auto attr = vm->getattr(obj.ptr(), name._as<pkpy::Str>(), false);
if(attr) { return attr; }
return default_;
}
inline object getattr(const handle& obj, const char* name, const handle& default_) {
auto attr = vm->getattr(obj.ptr(), name, false);
if(attr) { return attr; }
return default_;
}
inline void setattr(const handle& obj, const handle& name, const handle& value) {
vm->setattr(obj.ptr(), name._as<pkpy::Str>(), value.ptr());
}
inline void setattr(const handle& obj, const char* name, const handle& value) {
vm->setattr(obj.ptr(), name, value.ptr());
}
template <typename T>
inline bool isinstance(const handle& obj) {
return type_visitor::check<T>(obj);
}
template <>
inline bool isinstance<handle>(const handle&) = delete;
inline bool isinstance(const handle& obj, const handle& type) {
return vm->isinstance(obj.ptr(), type._as<pkpy::Type>());
}
inline int64_t hash(const handle& obj) { return vm->py_hash(obj.ptr()); }
namespace impl {
template <typename T, typename SFINAE = void>
struct type_caster;
}
template <typename T>
handle cast(T&& value, return_value_policy policy, handle parent) {
// decay_t can resolve c-array type, but remove_cv_ref_t can't.
using underlying_type = std::decay_t<T>;
if constexpr(std::is_convertible_v<underlying_type, handle>) {
return std::forward<T>(value);
} else {
static_assert(!is_multiple_pointer_v<underlying_type>, "multiple pointer is not supported.");
static_assert(!std::is_void_v<std::remove_pointer_t<underlying_type>>,
"void* is not supported, consider using py::capsule.");
// resolve for automatic policy.
if(policy == return_value_policy::automatic) {
policy = std::is_pointer_v<underlying_type> ? return_value_policy::take_ownership
: std::is_lvalue_reference_v<T&&> ? return_value_policy::copy
: return_value_policy::move;
} else if(policy == return_value_policy::automatic_reference) {
policy = std::is_pointer_v<underlying_type> ? return_value_policy::reference
: std::is_lvalue_reference_v<T&&> ? return_value_policy::copy
: return_value_policy::move;
}
return impl::type_caster<underlying_type>::cast(std::forward<T>(value), policy, parent);
}
}
template <typename T>
T cast(const handle& obj, bool convert) {
assert(obj.ptr() != nullptr);
impl::type_caster<T> caster = {};
if(caster.load(obj, convert)) {
return caster.value;
} else {
std::string msg = "cast python instance to c++ failed, ";
msg += "obj type is: {";
msg += type::of(obj).name();
msg += "}, target type is: {";
msg += type_name<T>();
msg += "}.";
vm->TypeError(msg);
PK_UNREACHABLE();
}
}
struct kwargs_proxy {
handle value;
};
struct args_proxy {
handle value;
kwargs_proxy operator* () { return kwargs_proxy{value}; }
};
template <typename Derived>
args_proxy interface<Derived>::operator* () const {
return args_proxy{ptr()};
}
template <typename... Args>
handle interpreter::vectorcall(const handle& callable, const handle& self, const Args&... args) {
vm->s_data.push(callable.ptr());
#if PK_VERSION_MAJOR == 2
vm->s_data.push(self ? self.ptr() : PY_NULL);
#else
vm->s_data.push(self ? self.ptr() : pkpy::PY_NULL);
#endif
int argc = 0;
int kwargsc = 0;
auto push_arg = [&](const handle& value) {
assert(value);
vm->s_data.push(value.ptr());
argc++;
};
auto push_named_arg = [&](std::string_view name, const handle& value) {
assert(value);
vm->s_data.push(int_(pkpy::StrName(name).index).ptr());
vm->s_data.push(value.ptr());
kwargsc++;
};
auto foreach_ = [&](const auto& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr(std::is_convertible_v<T, handle>) {
push_arg(arg);
} else if constexpr(std::is_same_v<T, args_proxy>) {
pybind11::tuple args = arg.value.template cast<pybind11::tuple>();
for(auto item: args) {
push_arg(item);
}
} else if constexpr(std::is_same_v<T, pybind11::arg>) {
push_named_arg(arg.name, arg.default_);
} else if constexpr(std::is_same_v<T, kwargs_proxy>) {
pybind11::dict kwargs = arg.value.template cast<pybind11::dict>();
for(auto item: kwargs) {
str name = item.first.template cast<str>();
push_named_arg(name, item.second);
}
} else {
static_assert(dependent_false<T>, "unsupported type");
}
};
(foreach_(args), ...);
return vm->vectorcall(argc, kwargsc);
}
template <typename Derived>
template <return_value_policy policy, typename... Args>
inline object interface<Derived>::operator() (Args&&... args) const {
auto _cast = [&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr(std::is_same_v<T, pybind11::arg> || std::is_same_v<T, kwargs_proxy> ||
std::is_same_v<T, args_proxy> || std::is_convertible_v<T, handle>) {
return arg;
} else {
return pybind11::cast(std::forward<decltype(arg)>(arg), policy);
}
};
return interpreter::vectorcall(ptr(), handle(), _cast(std::forward<Args>(args))...);
}
template <typename... Args>
void print(Args&&... args) {
handle print = getattr(vm->builtins, "print");
print(std::forward<Args>(args)...);
}
} // namespace pybind11

View File

@ -0,0 +1,174 @@
#pragma once
#include "instance.h"
#include "accessor.h"
namespace pybind11::impl {
using pkpy::is_floating_point_v;
using pkpy::is_integral_v;
template <typename T>
constexpr inline bool is_string_v =
std::is_same_v<T, const char*> || std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>;
template <typename T>
constexpr bool is_pointer_v = std::is_pointer_v<T> && !std::is_same_v<T, char*> && !std::is_same_v<T, const char*>;
template <typename T, typename>
struct type_caster;
template <>
struct type_caster<bool> {
bool value;
bool load(const handle& src, bool) {
if(isinstance<pybind11::bool_>(src)) {
value = pkpy::_py_cast<bool>(vm, src.ptr());
return true;
}
return false;
}
static handle cast(bool src, return_value_policy, handle) { return src ? vm->True : vm->False; }
};
template <typename T>
struct type_caster<T, std::enable_if_t<is_integral_v<T>>> {
T value;
bool load(const handle& src, bool convert) {
if(isinstance<pybind11::int_>(src)) {
value = pkpy::_py_cast<T>(vm, src.ptr());
return true;
}
return false;
}
static handle cast(T src, return_value_policy, handle) { return pkpy::py_var(vm, src); }
};
template <typename T>
struct type_caster<T, std::enable_if_t<is_floating_point_v<T>>> {
T value;
bool load(const handle& src, bool convert) {
if(isinstance<pybind11::float_>(src)) {
value = pkpy::_py_cast<T>(vm, src.ptr());
return true;
}
if(convert && isinstance<pybind11::int_>(src)) {
value = pkpy::_py_cast<int64_t>(vm, src.ptr());
return true;
}
return false;
}
static handle cast(T src, return_value_policy, handle) { return pkpy::py_var(vm, src); }
};
template <typename T>
struct type_caster<T, std::enable_if_t<is_string_v<T>>> {
T value;
bool load(const handle& src, bool) {
if(isinstance<pybind11::str>(src)) {
auto& str = src._as<pkpy::Str>();
if constexpr(std::is_same_v<T, std::string>) {
value = str;
} else if constexpr(std::is_same_v<T, std::string_view>) {
value = str;
} else if constexpr(std::is_same_v<T, const char*>) {
value = str.c_str();
}
return true;
}
return false;
}
template <typename U>
static handle cast(U&& src, return_value_policy, handle) {
return str(std::forward<U>(src));
}
};
template <typename T>
struct type_caster<T, std::enable_if_t<is_pyobject_v<T>>> {
T value;
bool load(const handle& src, bool) {
if(isinstance<T>(src)) {
value = src;
return true;
}
return false;
}
static handle cast(const handle& src, return_value_policy, handle) { return src; }
};
template <typename T, typename>
struct type_caster {
struct value_wrapper {
T* pointer;
operator T& () { return *pointer; }
};
value_wrapper value;
using underlying_type = std::remove_pointer_t<decltype(value.pointer)>;
bool load(handle src, bool convert) {
if(isinstance<underlying_type>(src)) {
auto& i = src._as<instance>();
value.pointer = &i._as<underlying_type>();
return true;
}
return false;
}
template <typename U>
static handle cast(U&& value, return_value_policy policy, const handle& parent = handle()) {
const auto& info = typeid(underlying_type);
auto type = type_visitor::type<underlying_type>();
return instance::create(std::forward<U>(value), type, policy, parent.ptr());
// TODO: support implicit cast
}
};
template <typename T>
struct type_caster<T, std::enable_if_t<is_pointer_v<T> || std::is_reference_v<T>>> {
using underlying =
std::remove_cv_t<std::conditional_t<is_pointer_v<T>, std::remove_pointer_t<T>, std::remove_reference_t<T>>>;
struct wrapper {
type_caster<underlying> caster;
operator T () {
if constexpr(std::is_pointer_v<T>) {
return caster.value.pointer;
} else {
return caster.value;
}
}
};
wrapper value;
bool load(const handle& src, bool convert) { return value.caster.load(src, convert); }
template <typename U>
static handle cast(U&& value, return_value_policy policy, const handle& parent) {
return type_caster<underlying>::cast(std::forward<U>(value), policy, parent);
}
};
} // namespace pybind11

View File

@ -0,0 +1,207 @@
#pragma once
#include "module.h"
#include <vector>
namespace pybind11 {
struct dynamic_attr {};
template <typename T, typename Base = void>
class class_ : public type {
protected:
handle m_scope;
public:
using type::type;
using underlying_type = T;
template <typename... Args>
class_(const handle& scope, const char* name, const Args&... args) :
m_scope(scope), type(type_visitor::create<T, Base>(scope, name)) {
auto& info = type_info::of<T>();
info.name = name;
// bind __new__
interpreter::bind_func(m_ptr, pkpy::__new__, -1, [](pkpy::VM* vm, pkpy::ArgsView args) {
auto cls = handle(args[0])._as<pkpy::Type>();
// check if the class has constructor, if not, raise error
if(vm->find_name_in_mro(cls, pkpy::__init__) == nullptr) {
vm->RuntimeError("if you want to create instance of bound class, you must bind constructor for it");
}
auto var = instance::create(cls, &type_info::of<T>());
if constexpr(types_count_v<dynamic_attr, Args...> != 0) {
#if PK_VERSION_MAJOR == 2
var.get()->_attr = new pkpy::NameDict();
#else
var->_enable_instance_dict();
#endif
}
return var;
});
}
/// bind constructor
template <typename... Args, typename... Extra>
class_& def(impl::constructor<Args...>, const Extra&... extra) {
if constexpr(!std::is_constructible_v<T, Args...>) {
static_assert(std::is_constructible_v<T, Args...>, "Invalid constructor arguments");
} else {
impl::bind_function<true>(
*this,
"__init__",
[](T* self, Args... args) {
new (self) T(args...);
},
pkpy::BindType::DEFAULT,
extra...);
return *this;
}
}
template <typename Fn, typename... Extra>
class_& def(impl::factory<Fn> factory, const Extra&... extra) {
using ret = callable_return_t<Fn>;
if constexpr(!std::is_same_v<T, ret>) {
static_assert(std::is_same_v<T, ret>, "Factory function must return the class type");
} else {
impl::bind_function<true>(*this, "__init__", factory.make(), pkpy::BindType::DEFAULT, extra...);
return *this;
}
}
/// bind member function
template <typename Fn, typename... Extra>
class_& def(const char* name, Fn&& f, const Extra&... extra) {
using first = remove_cvref_t<std::tuple_element_t<0, callable_args_t<remove_cvref_t<Fn>>>>;
constexpr bool is_first_base_of_v = std::is_base_of_v<first, T> || std::is_same_v<first, T>;
if constexpr(!is_first_base_of_v) {
static_assert(is_first_base_of_v,
"If you want to bind member function, the first argument must be the base class");
} else {
impl::bind_function<true>(*this, name, std::forward<Fn>(f), pkpy::BindType::DEFAULT, extra...);
}
return *this;
}
/// bind operators
template <typename Operator, typename... Extras>
class_& def(Operator op, const Extras&... extras) {
op.execute(*this, extras...);
return *this;
}
// TODO: factory function
/// bind static function
template <typename Fn, typename... Extra>
class_& def_static(const char* name, Fn&& f, const Extra&... extra) {
impl::bind_function<false>(*this, name, std::forward<Fn>(f), pkpy::BindType::STATICMETHOD, extra...);
return *this;
}
template <typename MP, typename... Extras>
class_& def_readwrite(const char* name, MP mp, const Extras&... extras) {
if constexpr(!std::is_member_object_pointer_v<MP>) {
static_assert(std::is_member_object_pointer_v<MP>, "def_readwrite only supports pointer to data member");
} else {
impl::bind_property(*this, name, mp, mp, extras...);
}
return *this;
}
template <typename MP, typename... Extras>
class_& def_readonly(const char* name, MP mp, const Extras&... extras) {
if constexpr(!std::is_member_object_pointer_v<MP>) {
static_assert(std::is_member_object_pointer_v<MP>, "def_readonly only supports pointer to data member");
} else {
impl::bind_property(*this, name, mp, nullptr, extras...);
}
return *this;
}
template <typename Getter, typename Setter, typename... Extras>
class_& def_property(const char* name, Getter&& g, Setter&& s, const Extras&... extras) {
impl::bind_property(*this, name, std::forward<Getter>(g), std::forward<Setter>(s), extras...);
return *this;
}
template <typename Getter, typename... Extras>
class_& def_property_readonly(const char* name, Getter&& mp, const Extras&... extras) {
impl::bind_property(*this, name, std::forward<Getter>(mp), nullptr, extras...);
return *this;
}
template <typename Var, typename... Extras>
class_& def_readwrite_static(const char* name, Var& mp, const Extras&... extras) {
static_assert(
dependent_false<Var>,
"define static properties requires metaclass. This is a complex feature with few use cases, so it may never be implemented.");
return *this;
}
template <typename Var, typename... Extras>
class_& def_readonly_static(const char* name, Var& mp, const Extras&... extras) {
static_assert(
dependent_false<Var>,
"define static properties requires metaclass. This is a complex feature with few use cases, so it may never be implemented.");
return *this;
}
template <typename Getter, typename Setter, typename... Extras>
class_& def_property_static(const char* name, Getter&& g, Setter&& s, const Extras&... extras) {
static_assert(
dependent_false<Getter>,
"define static properties requires metaclass. This is a complex feature with few use cases, so it may never be implemented.");
return *this;
}
};
template <typename T, typename... Others>
class enum_ : public class_<T, Others...> {
std::vector<std::pair<const char*, handle>> m_values;
public:
using Base = class_<T, Others...>;
using class_<T, Others...>::class_;
template <typename... Args>
enum_(const handle& scope, const char* name, Args&&... args) :
class_<T, Others...>(scope, name, std::forward<Args>(args)...) {
Base::def(init([](int value) {
return static_cast<T>(value);
}));
Base::def("__eq__", [](T& self, T& other) {
return self == other;
});
Base::def_property_readonly("value", [](T& self) {
return int_(static_cast<std::underlying_type_t<T>>(self));
});
}
enum_& value(const char* name, T value) {
handle var = pybind11::cast(value, return_value_policy::copy);
setattr(*this, name, var);
m_values.emplace_back(name, var);
return *this;
}
enum_& export_values() {
for(auto& [name, value]: m_values) {
setattr(Base::m_scope, name, value);
}
return *this;
}
};
} // namespace pybind11

View File

@ -0,0 +1,589 @@
#pragma once
#include "cast.h"
#include <map>
namespace pybind11 {
// append the overload to the beginning of the overload list
struct prepend {};
namespace impl {
template <typename... Args>
struct constructor {};
template <typename Fn, typename Args = callable_args_t<Fn>>
struct factory;
template <typename Fn, typename... Args>
struct factory<Fn, std::tuple<Args...>> {
Fn fn;
auto make() {
using Self = callable_return_t<Fn>;
return [fn = std::move(fn)](Self* self, Args... args) {
new (self) Self(fn(args...));
};
}
};
} // namespace impl
template <typename... Args>
impl::constructor<Args...> init() {
return {};
}
template <typename Fn>
impl::factory<Fn> init(Fn&& fn) {
return {std::forward<Fn>(fn)};
}
// TODO: support more customized tags
//
// template <std::size_t Nurse, std::size_t... Patients>
// struct keep_alive {};
//
// template <typename T>
// struct call_guard {
// static_assert(std::is_default_constructible_v<T>, "call_guard must be default constructible");
// };
//
// struct kw_only {};
//
// struct pos_only {};
class cpp_function : public function {
PYBIND11_TYPE_IMPLEMENT(function, pkpy::NativeFunc, vm->tp_native_func);
public:
template <typename Fn, typename... Extras>
cpp_function(Fn&& f, const Extras&... extras) {}
template <typename T>
decltype(auto) get_userdata_as() {
#if PK_VERSION_MAJOR == 2
return self()._userdata.as<T>();
#else
return self()._userdata._cast<T>();
#endif
}
template <typename T>
void set_userdata(T&& value) {
self()._userdata = std::forward<T>(value);
}
};
} // namespace pybind11
namespace pybind11::impl {
template <typename Callable,
typename Extra,
typename Args = callable_args_t<Callable>,
typename IndexSequence = std::make_index_sequence<std::tuple_size_v<Args>>>
struct template_parser;
class function_record {
private:
template <typename C, typename E, typename A, typename I>
friend struct template_parser;
struct arguments_t {
std::vector<pkpy::StrName> names;
std::vector<handle> defaults;
};
using destructor_t = void (*)(function_record*);
using wrapper_t = handle (*)(function_record&, pkpy::ArgsView, bool convert, handle parent);
static_assert(std::is_trivially_copyable_v<pkpy::StrName>);
private:
union {
void* data;
char buffer[16];
};
wrapper_t wrapper = nullptr;
function_record* next = nullptr;
arguments_t* arguments = nullptr;
destructor_t destructor = nullptr;
const char* signature = nullptr;
return_value_policy policy = return_value_policy::automatic;
public:
template <typename Fn, typename... Extras>
function_record(Fn&& f, const Extras&... extras) {
using Callable = std::decay_t<Fn>;
if constexpr(std::is_trivially_copyable_v<Callable> && sizeof(Callable) <= sizeof(buffer)) {
// if the callable object is trivially copyable and the size is less than 16 bytes, store it in the
// buffer
new (buffer) auto(std::forward<Fn>(f));
destructor = [](function_record* self) {
reinterpret_cast<Callable*>(self->buffer)->~Callable();
};
} else {
// otherwise, store it in the heap
data = new auto(std::forward<Fn>(f));
destructor = [](function_record* self) {
delete static_cast<Callable*>(self->data);
};
}
using Parser = template_parser<Callable, std::tuple<Extras...>>;
Parser::initialize(*this, extras...);
wrapper = Parser::wrapper;
}
function_record(const function_record&) = delete;
function_record& operator= (const function_record&) = delete;
function_record& operator= (function_record&&) = delete;
function_record(function_record&& other) noexcept {
std::memcpy(this, &other, sizeof(function_record));
std::memset(&other, 0, sizeof(function_record));
}
~function_record() {
if(destructor) { destructor(this); }
if(arguments) { delete arguments; }
if(next) { delete next; }
if(signature) { delete[] signature; }
}
void append(function_record* record) {
function_record* p = this;
while(p->next) {
p = p->next;
}
p->next = record;
}
template <typename T>
T& _as() {
if constexpr(std::is_trivially_copyable_v<T> && sizeof(T) <= sizeof(buffer)) {
return *reinterpret_cast<T*>(buffer);
} else {
return *static_cast<T*>(data);
}
}
handle operator() (pkpy::ArgsView view) {
function_record* p = this;
// foreach function record and call the function with not convert
while(p != nullptr) {
handle result = p->wrapper(*p, view, false, {});
if(result) { return result; }
p = p->next;
}
p = this;
// foreach function record and call the function with convert
while(p != nullptr) {
handle result = p->wrapper(*p, view, true, {});
if(result) { return result; }
p = p->next;
}
std::string msg = "no matching function found, function signature:\n";
std::size_t index = 0;
p = this;
while(p != nullptr) {
msg += " ";
msg += p->signature;
msg += "\n";
p = p->next;
}
vm->TypeError(msg);
PK_UNREACHABLE();
}
};
template <typename Fn, std::size_t... Is, typename... Args>
handle invoke(Fn&& fn,
std::index_sequence<Is...>,
std::tuple<impl::type_caster<Args>...>& casters,
return_value_policy policy,
handle parent) {
using underlying_type = std::decay_t<Fn>;
using return_type = callable_return_t<underlying_type>;
constexpr bool is_void = std::is_void_v<return_type>;
constexpr bool is_member_function_pointer = std::is_member_function_pointer_v<underlying_type>;
if constexpr(is_member_function_pointer) {
// helper function to unpack the arguments to call the member pointer
auto unpack = [&](class_type_t<underlying_type>& self, auto&... args) {
return (self.*fn)(args...);
};
if constexpr(!is_void) {
return pybind11::cast(unpack(std::get<Is>(casters).value...), policy, parent);
} else {
unpack(std::get<Is>(casters).value...);
return vm->None;
}
} else {
if constexpr(!is_void) {
return pybind11::cast(fn(std::get<Is>(casters).value...), policy, parent);
} else {
fn(std::get<Is>(casters).value...);
return vm->None;
}
}
}
struct arguments_info_t {
int argc = 0;
int args_pos = -1;
int kwargs_pos = -1;
};
struct extras_info_t {
int doc_pos = -1;
int named_argc = 0;
int policy_pos = -1;
};
template <typename Callable, typename... Extras, typename... Args, std::size_t... Is>
struct template_parser<Callable, std::tuple<Extras...>, std::tuple<Args...>, std::index_sequence<Is...>> {
constexpr static arguments_info_t parse_arguments() {
constexpr auto args_count = types_count_v<args, Args...>;
constexpr auto kwargs_count = types_count_v<kwargs, Args...>;
static_assert(args_count <= 1, "py::args can occur at most once");
static_assert(kwargs_count <= 1, "py::kwargs can occur at most once");
constexpr auto args_pos = type_index_v<args, Args...>;
constexpr auto kwargs_pos = type_index_v<kwargs, Args...>;
if constexpr(kwargs_count == 1) {
static_assert(kwargs_pos == sizeof...(Args) - 1, "py::kwargs must be the last argument");
// FIXME: temporarily, args and kwargs must be at the end of the arguments list
if constexpr(args_count == 1) {
static_assert(args_pos == kwargs_pos - 1, "py::args must be before py::kwargs");
}
}
return {sizeof...(Args), args_pos, kwargs_pos};
}
constexpr static extras_info_t parse_extras() {
constexpr auto doc_count = types_count_v<const char*, Extras...>;
constexpr auto policy_count = types_count_v<return_value_policy, Extras...>;
static_assert(doc_count <= 1, "doc can occur at most once");
static_assert(policy_count <= 1, "return_value_policy can occur at most once");
constexpr auto doc_pos = type_index_v<const char*, Extras...>;
constexpr auto policy_pos = type_index_v<return_value_policy, Extras...>;
constexpr auto named_argc = types_count_v<arg, Extras...>;
constexpr auto normal_argc =
sizeof...(Args) - (arguments_info.args_pos != -1) - (arguments_info.kwargs_pos != -1);
static_assert(named_argc == 0 || named_argc == normal_argc,
"named arguments must be the same as the number of function arguments");
return {doc_pos, named_argc, policy_pos};
}
constexpr inline static auto arguments_info = parse_arguments();
constexpr inline static auto extras_info = parse_extras();
static void initialize(function_record& record, const Extras&... extras) {
auto extras_tuple = std::make_tuple(extras...);
constexpr static bool has_named_args = (extras_info.named_argc > 0);
// set return value policy
if constexpr(extras_info.policy_pos != -1) { record.policy = std::get<extras_info.policy_pos>(extras_tuple); }
// TODO: set others
// set default arguments
if constexpr(has_named_args) {
record.arguments = new function_record::arguments_t();
auto add_arguments = [&](const auto& arg) {
if constexpr(std::is_same_v<pybind11::arg, remove_cvref_t<decltype(arg)>>) {
auto& arguments = *record.arguments;
arguments.names.emplace_back(arg.name);
arguments.defaults.emplace_back(arg.default_);
}
};
(add_arguments(extras), ...);
}
// set signature
{
std::string sig = "(";
std::size_t index = 0;
auto append = [&](auto _t) {
using T = pybind11_decay_t<typename decltype(_t)::type>;
if constexpr(std::is_same_v<T, args>) {
sig += "*args";
} else if constexpr(std::is_same_v<T, kwargs>) {
sig += "**kwargs";
} else if constexpr(has_named_args) {
sig += record.arguments->names[index].c_str();
sig += ": ";
sig += type_info::of<T>().name;
if(record.arguments->defaults[index]) {
sig += " = ";
sig += record.arguments->defaults[index].repr();
}
} else {
sig += "_: ";
sig += type_info::of<T>().name;
}
if(index + 1 < arguments_info.argc) { sig += ", "; }
index++;
};
(append(type_identity<Args>{}), ...);
sig += ")";
char* buffer = new char[sig.size() + 1];
std::memcpy(buffer, sig.data(), sig.size());
buffer[sig.size()] = '\0';
record.signature = buffer;
}
}
static handle wrapper(function_record& record, pkpy::ArgsView view, bool convert, handle parent) {
constexpr auto argc = arguments_info.argc;
constexpr auto named_argc = extras_info.named_argc;
constexpr auto args_pos = arguments_info.args_pos;
constexpr auto kwargs_pos = arguments_info.kwargs_pos;
constexpr auto normal_argc = argc - (args_pos != -1) - (kwargs_pos != -1);
// avoid gc call in bound function
vm->heap.gc_scope_lock();
// add 1 to avoid zero-size array when argc is 0
handle stack[argc + 1] = {};
// ensure the number of passed arguments is no greater than the number of parameters
if(args_pos == -1 && view.size() > normal_argc) { return handle(); }
// if have default arguments, load them
if constexpr(named_argc > 0) {
auto& defaults = record.arguments->defaults;
std::memcpy(stack, defaults.data(), defaults.size() * sizeof(handle));
}
// load arguments from call arguments
const auto size = std::min(view.size(), normal_argc);
std::memcpy(stack, view.begin(), size * sizeof(handle));
// pack the args
if constexpr(args_pos != -1) {
const auto n = std::max(view.size() - normal_argc, 0);
tuple args = tuple(n);
for(std::size_t i = 0; i < n; ++i) {
args[i] = view[normal_argc + i];
}
stack[args_pos] = args;
}
// resolve keyword arguments
const auto n = vm->s_data._sp - view.end();
int index = 0;
if constexpr(named_argc > 0) {
int arg_index = 0;
auto& arguments = *record.arguments;
while(arg_index < named_argc && index < n) {
const auto key = pkpy::_py_cast<pkpy::i64>(vm, view.end()[index]);
const auto value = view.end()[index + 1];
const auto name = pkpy::StrName(key);
auto& arg_name = record.arguments->names[arg_index];
if(name == arg_name) {
stack[arg_index] = value;
index += 2;
}
arg_index += 1;
}
}
// pack the kwargs
if constexpr(kwargs_pos != -1) {
dict kwargs;
while(index < n) {
const auto key = pkpy::_py_cast<pkpy::i64>(vm, view.end()[index]);
const str name = str(pkpy::StrName(key).sv());
kwargs[name] = view.end()[index + 1];
index += 2;
}
stack[kwargs_pos] = kwargs;
}
// if have rest keyword arguments, call fails
if(index != n) { return handle(); }
// check if all the arguments are valid
for(std::size_t i = 0; i < argc; ++i) {
if(!stack[i]) { return handle(); }
}
// ok, all the arguments are valid, call the function
std::tuple<impl::type_caster<Args>...> casters;
// check type compatibility
if(((std::get<Is>(casters).load(stack[Is], convert)) && ...)) {
return invoke(record._as<Callable>(), std::index_sequence<Is...>{}, casters, record.policy, parent);
}
return handle();
}
};
inline auto _wrapper(pkpy::VM* vm, pkpy::ArgsView view) {
auto&& record = unpack<function_record>(view);
return record(view).ptr();
}
template <bool is_method, typename Fn, typename... Extras>
handle bind_function(const handle& obj, const char* name, Fn&& fn, pkpy::BindType type, const Extras&... extras) {
// do not use cpp_function directly to avoid unnecessary reference count change
pkpy::PyVar var = obj.ptr();
cpp_function callable = var->attr().try_get(name);
function_record* record = nullptr;
if constexpr(is_method && types_count_v<arg, Extras...> > 0) {
// if the function is a method and has named arguments
// prepend self to the arguments list
record = new function_record(std::forward<Fn>(fn), arg("self"), extras...);
} else {
record = new function_record(std::forward<Fn>(fn), extras...);
}
if(!callable) {
// if the function is not bound yet, bind it
void* data = interpreter::take_ownership(std::move(*record));
callable = interpreter::bind_func(var, name, -1, _wrapper, data, type);
} else {
// if the function is already bound, append the new record to the function
function_record* last = callable.get_userdata_as<function_record*>();
if constexpr((types_count_v<prepend, Extras...> != 0)) {
// if prepend is specified, append the new record to the beginning of the list
callable.set_userdata(record);
record->append(last);
} else {
// otherwise, append the new record to the end of the list
last->append(record);
}
}
return callable;
}
} // namespace pybind11::impl
namespace pybind11::impl {
template <typename Getter>
pkpy::PyVar getter_wrapper(pkpy::VM* vm, pkpy::ArgsView view) {
handle result = vm->None;
auto&& getter = unpack<Getter>(view);
constexpr auto policy = return_value_policy::reference_internal;
if constexpr(std::is_member_pointer_v<Getter>) {
using Self = class_type_t<Getter>;
auto& self = handle(view[0])._as<instance>()._as<Self>();
if constexpr(std::is_member_object_pointer_v<Getter>) {
// specialize for pointer to data member
result = cast(self.*getter, policy, view[0]);
} else {
// specialize for pointer to member function
result = cast((self.*getter)(), policy, view[0]);
}
} else {
// specialize for function pointer and lambda
using Self = remove_cvref_t<std::tuple_element_t<0, callable_args_t<Getter>>>;
auto& self = handle(view[0])._as<instance>()._as<Self>();
result = cast(getter(self), policy, view[0]);
}
return result.ptr();
}
template <typename Setter>
pkpy::PyVar setter_wrapper(pkpy::VM* vm, pkpy::ArgsView view) {
auto&& setter = unpack<Setter>(view);
if constexpr(std::is_member_pointer_v<Setter>) {
using Self = class_type_t<Setter>;
auto& self = handle(view[0])._as<instance>()._as<Self>();
if constexpr(std::is_member_object_pointer_v<Setter>) {
// specialize for pointer to data member
impl::type_caster<member_type_t<Setter>> caster;
if(caster.load(view[1], true)) {
self.*setter = caster.value;
return vm->None;
}
} else {
// specialize for pointer to member function
impl::type_caster<std::tuple_element_t<1, callable_args_t<Setter>>> caster;
if(caster.load(view[1], true)) {
(self.*setter)(caster.value);
return vm->None;
}
}
} else {
// specialize for function pointer and lambda
using Self = remove_cvref_t<std::tuple_element_t<0, callable_args_t<Setter>>>;
auto& self = handle(view[0])._as<instance>()._as<Self>();
impl::type_caster<std::tuple_element_t<1, callable_args_t<Setter>>> caster;
if(caster.load(view[1], true)) {
setter(self, caster.value);
return vm->None;
}
}
vm->TypeError("Unexpected argument type");
PK_UNREACHABLE();
}
template <typename Getter, typename Setter, typename... Extras>
handle bind_property(const handle& obj, const char* name, Getter&& getter_, Setter&& setter_, const Extras&... extras) {
handle getter = none();
handle setter = none();
using Wrapper = pkpy::PyVar (*)(pkpy::VM*, pkpy::ArgsView);
constexpr auto create = [](Wrapper wrapper, int argc, auto&& f) {
if constexpr(need_host<remove_cvref_t<decltype(f)>>) {
// otherwise, store it in the type_info
void* data = interpreter::take_ownership(std::forward<decltype(f)>(f));
// store the index in the object
return vm->heap.gcnew<pkpy::NativeFunc>(vm->tp_native_func, wrapper, argc, data);
} else {
// if the function is trivially copyable and the size is less than 16 bytes, store it in the object
// directly
return vm->heap.gcnew<pkpy::NativeFunc>(vm->tp_native_func, wrapper, argc, f);
}
};
getter = create(impl::getter_wrapper<std::decay_t<Getter>>, 1, std::forward<Getter>(getter_));
if constexpr(!std::is_same_v<Setter, std::nullptr_t>) {
setter = create(impl::setter_wrapper<std::decay_t<Setter>>, 2, std::forward<Setter>(setter_));
}
handle property = pybind11::property(getter, setter);
setattr(obj, name, property);
return property;
}
} // namespace pybind11::impl

View File

@ -0,0 +1,142 @@
#pragma once
#include "kernel.h"
namespace pybind11 {
struct type_info {
std::string_view name;
std::size_t size;
std::size_t alignment;
void (*destructor)(void*);
const std::type_info* type;
template <typename T>
static type_info& of() {
static_assert(!std::is_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>,
"T must not be a reference type or const type.");
static type_info info = {
type_name<T>(),
sizeof(T),
alignof(T),
[](void* ptr) {
((T*)ptr)->~T();
operator delete (ptr);
},
&typeid(T),
};
return info;
}
};
// all registered C++ class will be ensured as instance type.
class instance {
public:
// use to record the type information of C++ class.
private:
enum Flag {
None = 0,
Own = 1 << 0, // if the instance is owned by C++ side.
Ref = 1 << 1, // need to mark the parent object.
};
Flag flag;
void* data;
const type_info* type;
pkpy::PyVar parent;
public:
instance() noexcept : flag(Flag::None), data(nullptr), type(nullptr), parent(nullptr) {}
instance(const instance&) = delete;
instance(instance&& other) noexcept : flag(other.flag), data(other.data), type(other.type), parent(other.parent) {
other.flag = Flag::None;
other.data = nullptr;
other.type = nullptr;
other.parent = nullptr;
}
static pkpy::PyVar create(pkpy::Type type, const type_info* info) noexcept {
instance instance;
instance.type = info;
instance.data = operator new (info->size);
instance.flag = Flag::Own;
return vm->heap.gcnew<pybind11::instance>(type, std::move(instance));
}
template <typename T>
static pkpy::PyVar create(T&& value,
pkpy::Type type,
return_value_policy policy = return_value_policy::automatic_reference,
pkpy::PyVar parent = nullptr) noexcept {
using underlying_type = remove_cvref_t<T>;
auto& _value = [&]() -> auto& {
// note that, pybind11 will ignore the const qualifier.
// in fact, try to modify a const value will result in undefined behavior.
if constexpr(std::is_pointer_v<underlying_type>) {
return *reinterpret_cast<underlying_type*>(value);
} else {
return const_cast<underlying_type&>(value);
}
}();
using primary = std::remove_pointer_t<underlying_type>;
instance instance;
instance.type = &type_info::of<primary>();
if(policy == return_value_policy::take_ownership) {
instance.data = &_value;
instance.flag = Flag::Own;
} else if(policy == return_value_policy::copy) {
if constexpr(std::is_copy_constructible_v<primary>) {
instance.data = ::new auto(_value);
instance.flag = Flag::Own;
} else {
std::string msg = "cannot use copy policy on non-copyable type: ";
msg += type_name<primary>();
vm->RuntimeError(msg);
}
} else if(policy == return_value_policy::move) {
if constexpr(std::is_move_constructible_v<primary>) {
instance.data = ::new auto(std::move(_value));
instance.flag = Flag::Own;
} else {
std::string msg = "cannot use move policy on non-moveable type: ";
msg += type_name<primary>();
vm->RuntimeError(msg);
}
} else if(policy == return_value_policy::reference) {
instance.data = &_value;
instance.flag = Flag::None;
} else if(policy == return_value_policy::reference_internal) {
instance.data = &_value;
instance.flag = Flag::Ref;
instance.parent = parent;
}
return vm->heap.gcnew<pybind11::instance>(type, std::move(instance));
}
~instance() {
if(flag & Flag::Own) { type->destructor(data); }
}
template <typename T>
T& _as() noexcept {
return *static_cast<T*>(data);
}
#if PK_VERSION_MAJOR == 2
void _gc_mark(pkpy::VM* vm) const noexcept {
if(parent && (flag & Flag::Ref)) { PK_OBJ_MARK(parent); }
}
#else
void _gc_mark() const noexcept {
if(parent && (flag & Flag::Ref)) { PK_OBJ_MARK(parent); }
}
#endif
};
} // namespace pybind11

View File

@ -0,0 +1,219 @@
#pragma once
#include <vector>
#include <cassert>
#include <pocketpy.h>
#include "type_traits.h"
namespace pybind11::impl {
struct capsule {
void* ptr;
void (*destructor)(void*);
template <typename T, typename = std::enable_if_t<!(std::is_same_v<remove_cvref_t<T>, capsule>)>>
capsule(T&& value) :
ptr(new auto(std::forward<T>(value))), destructor([](void* ptr) {
delete static_cast<std::decay_t<T>*>(ptr);
}) {}
capsule(void* ptr, void (*destructor)(void*)) : ptr(ptr), destructor(destructor) {}
capsule(const capsule&) = delete;
capsule(capsule&& other) noexcept : ptr(other.ptr), destructor(other.destructor) {
other.ptr = nullptr;
other.destructor = nullptr;
}
~capsule() {
if(ptr && destructor) destructor(ptr);
}
};
} // namespace pybind11::impl
namespace pybind11 {
class handle;
class object;
class iterator;
class str;
struct arg;
struct args_proxy;
struct kwargs_proxy;
inline pkpy::VM* vm = nullptr;
class interpreter {
inline static std::vector<impl::capsule>* _capsules = nullptr;
inline static std::vector<void (*)()>* _init = nullptr;
public:
inline static void initialize(bool enable_os = true) {
if(vm == nullptr) {
vm = new pkpy::VM();
if(_init != nullptr) {
for(auto& fn: *_init)
fn();
}
}
}
inline static void finalize() {
if(_capsules != nullptr) {
delete _capsules;
_capsules = nullptr;
}
if(vm != nullptr) {
delete vm;
vm = nullptr;
}
}
template <typename T>
inline static void* take_ownership(T&& value) {
if(_capsules == nullptr) _capsules = new std::vector<impl::capsule>();
_capsules->emplace_back(std::forward<T>(value));
return _capsules->back().ptr;
}
inline static void register_init(void (*init)()) {
if(_init == nullptr) _init = new std::vector<void (*)()>();
_init->push_back(init);
}
inline static pkpy::PyVar bind_func(pkpy::PyVar scope,
pkpy::StrName name,
int argc,
pkpy::NativeFuncC fn,
pkpy::any any = {},
pkpy::BindType type = pkpy::BindType::DEFAULT) {
#if PK_VERSION_MAJOR == 2
return vm->bind_func(scope.get(), name, argc, fn, any, type);
#else
return vm->bind_func(scope, name, argc, fn, std::move(any), type);
#endif
}
template <typename... Args>
inline static handle vectorcall(const handle& self, const handle& func, const Args&... args);
};
template <typename T>
constexpr inline bool need_host = !(std::is_trivially_copyable_v<T> && (sizeof(T) <= 8));
template <typename T>
decltype(auto) unpack(pkpy::ArgsView view) {
if constexpr(need_host<T>) {
void* data = pkpy::lambda_get_userdata<void*>(view.begin());
return *static_cast<T*>(data);
} else {
return pkpy::lambda_get_userdata<T>(view.begin());
}
}
template <typename policy>
class accessor;
namespace policy {
struct attr;
struct item;
struct tuple;
struct list;
struct dict;
} // namespace policy
using attr_accessor = accessor<policy::attr>;
using item_accessor = accessor<policy::item>;
using tuple_accessor = accessor<policy::tuple>;
using list_accessor = accessor<policy::list>;
using dict_accessor = accessor<policy::dict>;
template <typename T>
T cast(const handle& obj, bool convert = false);
enum class return_value_policy : uint8_t {
/**
* This is the default return value policy, which falls back to the policy
* return_value_policy::take_ownership when the return value is a pointer.
* Otherwise, it uses return_value::move or return_value::copy for rvalue
* and lvalue references, respectively. See below for a description of what
* all of these different policies do.
*/
automatic = 0,
/**
* As above, but use policy return_value_policy::reference when the return
* value is a pointer. This is the default conversion policy for function
* arguments when calling Python functions manually from C++ code (i.e. via
* handle::operator()). You probably won't need to use this.
*/
automatic_reference,
/**
* Reference an existing object (i.e. do not create a new copy) and take
* ownership. Python will call the destructor and delete operator when the
* object's reference count reaches zero. Undefined behavior ensues when
* the C++ side does the same..
*/
take_ownership,
/**
* Create a new copy of the returned object, which will be owned by
* Python. This policy is comparably safe because the lifetimes of the two
* instances are decoupled.
*/
copy,
/**
* Use std::move to move the return value contents into a new instance
* that will be owned by Python. This policy is comparably safe because the
* lifetimes of the two instances (move source and destination) are
* decoupled.
*/
move,
/**
* Reference an existing object, but do not take ownership. The C++ side
* is responsible for managing the object's lifetime and deallocating it
* when it is no longer used. Warning: undefined behavior will ensue when
* the C++ side deletes an object that is still referenced and used by
* Python.
*/
reference,
/**
* This policy only applies to methods and properties. It references the
* object without taking ownership similar to the above
* return_value_policy::reference policy. In contrast to that policy, the
* function or property's implicit this argument (called the parent) is
* considered to be the the owner of the return value (the child).
* pybind11 then couples the lifetime of the parent to the child via a
* reference relationship that ensures that the parent cannot be garbage
* collected while Python is still using the child. More advanced
* variations of this scheme are also possible using combinations of
* return_value_policy::reference and the keep_alive call policy
*/
reference_internal
};
struct empty {};
template <typename... Args>
void print(Args&&... args);
class object;
template <typename T>
constexpr inline bool is_pyobject_v = std::is_base_of_v<object, T>;
#if PK_VERSION_MAJOR == 2
using error_already_set = pkpy::TopLevelException;
#else
using error_already_set = pkpy::Exception;
#endif
inline void setattr(const handle& obj, const handle& name, const handle& value);
inline void setattr(const handle& obj, const char* name, const handle& value);
} // namespace pybind11

View File

@ -0,0 +1,50 @@
#pragma once
#include "cpp_function.h"
namespace pybind11 {
class module_ : public object {
public:
using object::object;
static module_ __main__() { return vm->_main; }
static module_ import(const char* name) {
if(name == std::string_view{"__main__"}) {
return vm->_main;
} else {
return vm->py_import(name, false);
}
}
module_ def_submodule(const char* name, const char* doc = nullptr) {
// TODO: resolve package
//auto package = this->package()._as<pkpy::Str>() + "." + this->name()._as<pkpy::Str>();
auto fname = this->name()._as<pkpy::Str>() + "." + name;
auto m = vm->new_module(fname, "");
setattr(*this, name, m);
return m;
}
template <typename Fn, typename... Extras>
module_& def(const char* name, Fn&& fn, const Extras... extras) {
impl::bind_function<false>(*this, name, std::forward<Fn>(fn), pkpy::BindType::DEFAULT, extras...);
return *this;
}
};
#define PYBIND11_EMBEDDED_MODULE(name, variable) \
static void _pybind11_register_##name(pybind11::module_& variable); \
namespace pybind11::impl { \
auto _module_##name = [] { \
interpreter::register_init([] { \
pybind11::module_ m = vm->new_module(#name, ""); \
_pybind11_register_##name(m); \
}); \
return 1; \
}(); \
} \
static void _pybind11_register_##name(pybind11::module_& variable)
} // namespace pybind11

View File

@ -0,0 +1,272 @@
#pragma once
#include "instance.h"
namespace pybind11 {
template <typename Derived>
class interface {
private:
pkpy::PyVar ptr() const { return static_cast<const Derived*>(this)->ptr(); }
public:
bool is_none() const {
#if PK_VERSION_MAJOR == 2
return ptr().operator== (vm->None.get());
#else
return ptr() == vm->None;
#endif
}
bool is(const interface& other) const {
#if PK_VERSION_MAJOR == 2
return ptr().operator== (other.ptr().get());
#else
return ptr() == other.ptr();
#endif
}
bool in(const interface& other) const {
return pybind11::cast<bool>(vm->call(vm->py_op("contains"), other.ptr(), ptr()));
}
bool contains(const interface& other) const {
return pybind11::cast<bool>(vm->call(vm->py_op("contains"), ptr(), other.ptr()));
}
protected:
attr_accessor attr(pkpy::StrName key) const;
public:
iterator begin() const;
iterator end() const;
attr_accessor attr(const char* key) const;
attr_accessor attr(const handle& key) const;
attr_accessor doc() const;
item_accessor operator[] (int index) const;
item_accessor operator[] (const char* key) const;
item_accessor operator[] (const handle& key) const;
args_proxy operator* () const;
object operator- () const;
object operator~() const;
template <return_value_policy policy = return_value_policy::automatic, typename... Args>
object operator() (Args&&... args) const;
str package() const;
str name() const;
str repr() const;
public:
template <typename T>
T cast() const {
return pybind11::cast<T>(ptr());
}
// this is a internal function, use to interact with pocketpy python
template <typename T>
decltype(auto) _as() const {
static_assert(!std::is_reference_v<T>, "T must not be a reference type.");
if constexpr(std::is_same_v<T, empty>) {
return empty();
} else {
#if PK_VERSION_MAJOR == 2
if constexpr(pkpy::is_sso_v<T>) {
return pkpy::_py_cast<T>(vm, ptr());
} else {
return ptr().template obj_get<T>();
}
#else
return (((pkpy::Py_<T>*)ptr())->_value);
#endif
}
}
};
/// a lightweight wrapper for python objects
class handle : public interface<handle> {
protected:
mutable pkpy::PyVar m_ptr = nullptr;
public:
handle() = default;
handle(const handle& h) = default;
handle& operator= (const handle& other) = default;
handle(std::nullptr_t) = delete;
handle(pkpy::PyVar ptr) : m_ptr(ptr) {}
#if PK_VERSION_MAJOR == 2
handle(pkpy::PyObject* ptr) : m_ptr(ptr) {}
#endif
pkpy::PyVar ptr() const { return m_ptr; }
explicit operator bool () const { return m_ptr != nullptr; }
};
// a helper class to visit type
struct type_visitor {
template <typename T>
constexpr static bool is_type = std::is_same_v<pkpy::Type, std::decay_t<decltype(T::type_or_check())>>;
template <typename T>
static pkpy::Type type() {
if constexpr(is_pyobject_v<T>) {
if constexpr(is_type<T>) {
// for some type, they have according type in python, e.g. bool, int, float
// so just return the according type
return T::type_or_check();
} else {
// for other type, they don't have according type in python, like iterable, iterator
static_assert(dependent_false<T>, "type_or_check not defined");
}
} else {
#if PK_VERSION_MAJOR == 2
// for C++ type, lookup the type in the type map
auto type = vm->_cxx_typeid_map.try_get(typeid(T));
// if found, return the type
if(type) return *type;
#else
auto result = vm->_cxx_typeid_map.find(typeid(T));
if(result != vm->_cxx_typeid_map.end()) { return result->second; }
#endif
// if not found, raise error
std::string msg = "can not c++ instance cast to object, type: {";
msg += type_name<T>();
msg += "} is not registered.";
vm->TypeError(msg);
PK_UNREACHABLE();
}
}
template <typename T, typename Base = void>
static handle create(const handle& scope, const char* name, bool is_builtin = false) {
pkpy::Type type = vm->tp_object;
#if PK_VERSION_MAJOR == 2
pkpy::PyTypeInfo::Vt vt = pkpy::PyTypeInfo::Vt::get<instance>();
if(is_builtin) { vt = pkpy::PyTypeInfo::Vt::get<T>(); }
if constexpr(!std::is_same_v<Base, void>) {
type = type_visitor::type<Base>();
vt = {};
}
handle result = vm->new_type_object(scope.ptr().get(), name, type, true, vt);
if(!is_builtin) { vm->_cxx_typeid_map.insert(typeid(T), result._as<pkpy::Type>()); }
#else
if constexpr(!std::is_same_v<Base, void>) { type = type_visitor::type<Base>(); }
handle result = vm->new_type_object(scope.ptr(), name, type, true);
if(!is_builtin) (vm->_cxx_typeid_map.try_emplace(typeid(T), result._as<pkpy::Type>()));
#endif
// set __module__
setattr(scope, name, result);
return result;
}
template <typename T>
static bool check(const handle& obj) {
if constexpr(is_pyobject_v<T>) {
if constexpr(is_type<T>) {
return vm->isinstance(obj.ptr(), T::type_or_check());
} else {
// some type, like iterable, iterator, they don't have according type in python
// but they have a function to check the type, then just call the function
return T::type_or_check(obj);
}
} else {
return vm->isinstance(obj.ptr(), type<T>());
}
}
};
// undef in pybind11.h
#define PYBIND11_TYPE_IMPLEMENT(parent, name, tp) \
\
private: \
using underlying_type = name; \
\
inline static auto type_or_check = [] { \
return tp; \
}; \
\
decltype(auto) self() const { return _as<underlying_type>(); } \
\
template <typename... Args> \
static handle create(Args&&... args) { \
if constexpr(pkpy::is_sso_v<underlying_type>) { \
return pkpy::py_var(vm, std::forward<Args>(args)...); \
} else { \
return vm->heap.gcnew<underlying_type>(type_or_check(), std::forward<Args>(args)...); \
} \
} \
\
friend type_visitor; \
using parent::parent;
/*=============================================================================//
// pkpy does not use reference counts, so object is just fot API compatibility //
//=============================================================================*/
class object : public handle {
PYBIND11_TYPE_IMPLEMENT(handle, empty, vm->tp_object);
public:
object(const handle& h) : handle(h) {
// object is must not null ptr
assert(h.ptr() != nullptr);
}
};
// undef after usage
#define PYBIND11_BINARY_OPERATOR(OP, NAME) \
inline object operator OP (const handle& lhs, const handle& rhs) { return handle(vm->py_op(NAME))(lhs, rhs); }
#define PYBIND11_INPLACE_OPERATOR(OP, NAME) \
inline object operator OP (handle& lhs, const handle& rhs) { \
handle result = handle(vm->py_op(NAME))(lhs, rhs); \
return lhs = result; \
}
#define PYBIND11_BINARY_LOGIC_OPERATOR(OP, NAME) \
inline bool operator OP (const handle& lhs, const handle& rhs) { \
return pybind11::cast<bool>(handle(vm->py_op(NAME))(lhs, rhs)); \
}
PYBIND11_BINARY_OPERATOR(+, "add");
PYBIND11_BINARY_OPERATOR(-, "sub");
PYBIND11_BINARY_OPERATOR(*, "mul");
PYBIND11_BINARY_OPERATOR(/, "truediv");
PYBIND11_BINARY_OPERATOR(%, "mod");
PYBIND11_BINARY_OPERATOR(|, "or_");
PYBIND11_BINARY_OPERATOR(&, "and_");
PYBIND11_BINARY_OPERATOR(^, "xor");
PYBIND11_BINARY_OPERATOR(<<, "lshift");
PYBIND11_BINARY_OPERATOR(>>, "rshift");
PYBIND11_INPLACE_OPERATOR(+=, "iadd");
PYBIND11_INPLACE_OPERATOR(-=, "isub");
PYBIND11_INPLACE_OPERATOR(*=, "imul");
PYBIND11_INPLACE_OPERATOR(/=, "itruediv");
PYBIND11_INPLACE_OPERATOR(%=, "imod");
PYBIND11_INPLACE_OPERATOR(|=, "ior");
PYBIND11_INPLACE_OPERATOR(&=, "iand");
PYBIND11_INPLACE_OPERATOR(^=, "ixor");
PYBIND11_INPLACE_OPERATOR(<<=, "ilshift");
PYBIND11_INPLACE_OPERATOR(>>=, "irshift");
PYBIND11_BINARY_LOGIC_OPERATOR(==, "eq");
PYBIND11_BINARY_LOGIC_OPERATOR(!=, "ne");
PYBIND11_BINARY_LOGIC_OPERATOR(<, "lt");
PYBIND11_BINARY_LOGIC_OPERATOR(>, "gt");
PYBIND11_BINARY_LOGIC_OPERATOR(<=, "le");
PYBIND11_BINARY_LOGIC_OPERATOR(>=, "ge");
#undef PYBIND11_BINARY_OPERATOR
#undef PYBIND11_INPLACE_OPERATOR
#undef PYBIND11_BINARY_LOGIC_OPERATOR
}; // namespace pybind11

View File

@ -0,0 +1,198 @@
#pragma once
#include <tuple>
#include <string_view>
#include <type_traits>
namespace pybind11 {
template <typename T>
constexpr bool dependent_false = false;
template <typename T, typename Tuple>
struct tuple_push_front;
template <typename T, typename... Ts>
struct tuple_push_front<T, std::tuple<Ts...>> {
using type = std::tuple<T, Ts...>;
};
template <typename T, typename Tuple>
using tuple_push_front_t = typename tuple_push_front<T, Tuple>::type;
// traits for function types
template <typename Fn>
struct function_traits {
static_assert(dependent_false<Fn>, "unsupported function type");
};
#define PYBIND11_FUNCTION_TRAITS_SPECIALIZE(...) \
template <typename R, typename... Args> \
struct function_traits<R(Args...) __VA_ARGS__> { \
using return_type = R; \
using args_type = std::tuple<Args...>; \
constexpr static std::size_t args_count = sizeof...(Args); \
};
PYBIND11_FUNCTION_TRAITS_SPECIALIZE()
PYBIND11_FUNCTION_TRAITS_SPECIALIZE(&)
PYBIND11_FUNCTION_TRAITS_SPECIALIZE(const)
PYBIND11_FUNCTION_TRAITS_SPECIALIZE(const&)
PYBIND11_FUNCTION_TRAITS_SPECIALIZE(noexcept)
PYBIND11_FUNCTION_TRAITS_SPECIALIZE(& noexcept)
PYBIND11_FUNCTION_TRAITS_SPECIALIZE(const noexcept)
PYBIND11_FUNCTION_TRAITS_SPECIALIZE(const& noexcept)
#undef PYBIND11_FUNCTION_TRAITS_SPECIALIZE
template <typename T>
using function_return_t = typename function_traits<T>::return_type;
template <typename T>
using function_args_t = typename function_traits<T>::args_type;
template <typename T>
constexpr std::size_t function_args_count = function_traits<T>::args_count;
// traits for member pointers
template <typename T>
struct member_traits;
template <typename M, typename C>
struct member_traits<M C::*> {
using member_type = M;
using class_type = C;
};
template <typename T>
using member_type_t = typename member_traits<T>::member_type;
template <typename T>
using class_type_t = typename member_traits<T>::class_type;
// some traits for distinguishing between function pointers, member function pointers and
// functors
using std::is_member_function_pointer_v;
using std::is_member_object_pointer_v;
template <typename T>
constexpr inline bool is_function_pointer_v = std::is_function_v<std::remove_pointer_t<T>>;
template <typename T, typename U = void>
constexpr bool is_functor_v = false;
template <typename T>
constexpr inline bool is_functor_v<T, std::void_t<decltype(&T::operator())>> = true;
template <typename T, typename SFINAE = void>
struct callable_traits;
template <typename T>
struct callable_traits<T, std::enable_if_t<is_member_function_pointer_v<T>>> {
using args_type = tuple_push_front_t<class_type_t<T>&, function_args_t<member_type_t<T>>>;
using return_type = function_return_t<member_type_t<T>>;
};
template <typename T>
struct callable_traits<T, std::enable_if_t<is_function_pointer_v<T>>> {
using args_type = function_args_t<std::remove_pointer_t<T>>;
using return_type = function_return_t<std::remove_pointer_t<T>>;
};
template <typename T>
struct callable_traits<T, std::enable_if_t<is_functor_v<T>>> {
using args_type = function_args_t<member_type_t<decltype(&T::operator())>>;
using return_type = function_return_t<member_type_t<decltype(&T::operator())>>;
};
template <typename Callable>
using callable_args_t = typename callable_traits<Callable>::args_type;
template <typename Callable>
using callable_return_t = typename callable_traits<Callable>::return_type;
template <typename Callable>
constexpr std::size_t callable_args_count_v = std::tuple_size_v<callable_args_t<Callable>>;
template <typename T>
struct type_identity {
using type = T;
};
template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename T, typename... Ts>
constexpr inline std::size_t types_count_v = (std::is_same_v<T, Ts> + ...);
template <typename T>
constexpr inline std::size_t types_count_v<T> = 0;
template <typename T, typename... Ts>
constexpr inline int type_index_v = [] {
bool arr[sizeof...(Ts) + 1] = {std::is_same_v<T, Ts>...};
for(int i = 0; i < sizeof...(Ts); ++i) {
if(arr[i]) return i;
}
return -1;
}();
static_assert(types_count_v<int, int, float, int> == 2);
static_assert(types_count_v<int, float, double> == 0);
static_assert(type_index_v<int, int, float, int> == 0);
static_assert(type_index_v<float, int, float, int> == 1);
static_assert(type_index_v<int, float, double> == -1);
template <typename T>
constexpr inline bool is_multiple_pointer_v = std::is_pointer_v<T> && is_multiple_pointer_v<std::remove_pointer_t<T>>;
template <typename T>
using pybind11_decay_t = std::decay_t<std::remove_pointer_t<std::decay_t<T>>>;
template <typename T>
constexpr auto type_name() {
#if __GNUC__ || __clang__
std::string_view name = __PRETTY_FUNCTION__;
std::size_t start = name.find('=') + 2;
std::size_t end = name.size() - 1;
return std::string_view{name.data() + start, end - start};
#elif _MSC_VER
std::string_view name = __FUNCSIG__;
std::size_t start = name.find('<') + 1;
std::size_t end = name.rfind(">(");
name = std::string_view{name.data() + start, end - start};
start = name.find(' ');
return start == std::string_view::npos ? name : std::string_view{name.data() + start + 1, name.size() - start - 1};
#else
static_assert(false, "Unsupported compiler");
#endif
}
template <typename... Args>
struct overload_cast_t {
template <typename Return>
constexpr auto operator() (Return (*pf)(Args...)) const noexcept -> decltype(pf) {
return pf;
}
template <typename Return, typename Class>
constexpr auto operator() (Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept -> decltype(pmf) {
return pmf;
}
template <typename Return, typename Class>
constexpr auto operator() (Return (Class::*pmf)(Args...) const, std::true_type) const noexcept -> decltype(pmf) {
return pmf;
}
};
/// Syntax sugar for resolving overloaded function pointers:
/// - regular: static_cast<Return (Class::*)(Arg0, Arg1, Arg2)>(&Class::func)
/// - sweet: overload_cast<Arg0, Arg1, Arg2>(&Class::func)
template <typename... Args>
constexpr inline overload_cast_t<Args...> overload_cast;
/// Const member function selector for overload_cast
/// - regular: static_cast<Return (Class::*)(Arg) const>(&Class::func)
/// - sweet: overload_cast<Arg>(&Class::func, const_)
constexpr static auto const_ = std::true_type{};
} // namespace pybind11

View File

@ -0,0 +1,401 @@
#pragma once
#include "object.h"
namespace pybind11 {
template <typename T>
handle cast(T&& value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = {});
struct arg {
const char* name;
handle default_;
arg(const char* name) : name(name), default_() {}
template <typename T>
arg& operator= (T&& value) {
default_ = cast(std::forward<T>(value));
return *this;
}
};
// undef in pybind11.h
#define PYBIND11_REGISTER_INIT(func) \
static inline int _register = [] { \
interpreter::register_init(func); \
return 0; \
}();
class none : public object {
#if PK_VERSION_MAJOR == 2
PYBIND11_TYPE_IMPLEMENT(object, empty, vm->tp_none_type);
#else
PYBIND11_TYPE_IMPLEMENT(object, empty, [](const handle& obj) {
return obj.is_none();
});
#endif
public:
none() : object(vm->None) {}
};
/// corresponding to type in Python
class type : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::Type, vm->tp_type);
public:
template <typename T>
static handle handle_of() {
return type_visitor::type<T>();
}
template <typename T>
static type of() {
return type_visitor::type<T>();
}
static type of(const handle& obj) { return type(vm->_t(obj.ptr())); }
};
/// corresponding to bool in Python
class bool_ : public object {
PYBIND11_TYPE_IMPLEMENT(object, bool, vm->tp_bool);
public:
bool_(bool value) : object(create(value)) {}
operator bool () const { return self(); }
};
/// corresponding to int in Python
class int_ : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::i64, vm->tp_int);
public:
int_(int64_t value) : object(create(value)) {}
operator int64_t () const { return self(); }
};
/// corresponding to float in Python
class float_ : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::f64, vm->tp_float);
public:
float_(double value) : object(create(value)) {}
operator double () const { return self(); }
};
class iterable : public object {
PYBIND11_TYPE_IMPLEMENT(object, empty, [](const handle& obj) {
return vm->getattr(obj.ptr(), pkpy::__iter__, false) != nullptr;
});
};
class iterator : public object {
PYBIND11_TYPE_IMPLEMENT(object, empty, [](const handle& obj) {
return vm->getattr(obj.ptr(), pkpy::__next__, false) != nullptr &&
vm->getattr(obj.ptr(), pkpy::__iter__, false) != nullptr;
});
handle m_value;
iterator(pkpy::PyVar n, pkpy::PyVar s) : object(n), m_value(s) {}
public:
iterator(const handle& obj) : object(obj) { m_value = vm->py_next(obj.ptr()); }
iterator operator++ () {
m_value = vm->py_next(m_ptr);
return *this;
}
iterator operator++ (int) {
m_value = vm->py_next(m_ptr);
return *this;
}
const handle& operator* () const { return m_value; }
friend bool operator== (const iterator& lhs, const iterator& rhs) { return lhs.m_value.is(rhs.m_value); }
friend bool operator!= (const iterator& lhs, const iterator& rhs) { return !(lhs == rhs); }
static iterator sentinel() { return iterator(vm->None, vm->StopIteration); }
};
class str : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::Str, vm->tp_str);
public:
str(const char* c, int len) : object(create(c, len)) {};
str(const char* c = "") : str(c, strlen(c)) {}
str(const std::string& s) : str(s.data(), s.size()) {}
str(std::string_view sv) : str(sv.data(), sv.size()) {}
// explicit str(const bytes& b);
explicit str(handle h);
operator std::string_view () const { return self().sv(); }
template <typename... Args>
str format(Args&&... args) const;
};
// class bytes : public object {
// public:
// using object::object;
// };
// class bytearray : public object {
// public:
// using object::object;
// };
class tuple : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::Tuple, vm->tp_tuple);
public:
tuple(int n) : object(create(n)) {}
template <typename... Args, std::enable_if_t<(sizeof...(Args) > 1)>* = nullptr>
tuple(Args&&... args) : object(create(sizeof...(Args))) {
int index = 0;
((self()[index++] = pybind11::cast(std::forward<Args>(args)).ptr()), ...);
}
int size() const { return self().size(); }
bool empty() const { return size() == 0; }
tuple_accessor operator[] (int i) const;
};
class list : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::List, vm->tp_list)
public:
list() : object(create(0)) {}
list(int n) : object(create(n)) {}
template <typename... Args, std::enable_if_t<(sizeof...(Args) > 1)>* = nullptr>
list(Args&&... args) : object(create(sizeof...(Args))) {
int index = 0;
((self()[index++] = pybind11::cast(std::forward<Args>(args)).ptr()), ...);
}
int size() const { return self().size(); }
bool empty() const { return size() == 0; }
void clear() { self().clear(); }
list_accessor operator[] (int i) const;
void append(const handle& obj) { self().push_back(obj.ptr()); }
void extend(const handle& iterable) {
for(auto& item: iterable) {
append(item);
}
}
void insert(int index, const handle& obj) {
#if PK_VERSION_MAJOR == 2
const auto pos = self().begin() + index;
self().insert(pos, obj.ptr());
#else
self().insert(index, obj.ptr());
#endif
}
};
class slice : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::Slice, vm->tp_slice);
public:
};
// class set : public object {
// public:
// using object::object;
// // set() : object(vm->new_object<pkpy::Se>(pkpy::VM::tp_set), true) {}
// };
//
class dict : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::Dict, vm->tp_dict);
public:
#if PK_VERSION_MAJOR == 2
dict() : object(create()) {}
template <typename... Args, typename = std::enable_if_t<(std::is_same_v<remove_cvref_t<Args>, arg> && ...)>>
dict(Args&&... args) : object(create()) {
auto foreach_ = [&](pybind11::arg& arg) {
setitem(str(arg.name), arg.default_);
};
(foreach_(args), ...);
}
void setitem(const handle& key, const handle& value) { self().set(vm, key.ptr(), value.ptr()); }
handle getitem(const handle& key) const { return self().try_get(vm, key.ptr()); }
struct iterator {
pkpy_DictIter iter;
std::pair<handle, handle> value;
iterator operator++ () {
bool is_ended = pkpy_DictIter__next(&iter, (PyVar*)&value.first, (PyVar*)&value.second);
if(!is_ended) {
iter._dict = nullptr;
iter._index = -1;
}
return *this;
}
std::pair<handle, handle> operator* () const { return value; }
bool operator== (const iterator& other) const {
return iter._dict == other.iter._dict && iter._index == other.iter._index;
}
bool operator!= (const iterator& other) const { return !(*this == other); }
};
iterator begin() const {
iterator iter{self().iter(), {}};
++iter;
return iter;
}
iterator end() const { return {nullptr, -1}; }
#else
dict() : object(create(vm)) {}
template <typename... Args, typename = std::enable_if_t<(std::is_same_v<remove_cvref_t<Args>, arg> && ...)>>
dict(Args&&... args) : object(create(vm)) {
auto foreach_ = [&](pybind11::arg& arg) {
setitem(str(arg.name), arg.default_);
};
(foreach_(args), ...);
}
void setitem(const handle& key, const handle& value) { self().set(key.ptr(), value.ptr()); }
handle getitem(const handle& key) const { return self().try_get(key.ptr()); }
struct iterator {
pkpy::Dict::Item* items;
pkpy::Dict::ItemNode* nodes;
int index;
iterator operator++ () {
index = nodes[index].next;
if(index == -1) {
items = nullptr;
nodes = nullptr;
}
return *this;
}
std::pair<handle, handle> operator* () const { return {items[index].first, items[index].second}; }
bool operator== (const iterator& other) const {
return items == other.items && nodes == other.nodes && index == other.index;
}
bool operator!= (const iterator& other) const { return !(*this == other); }
};
iterator begin() const {
auto index = self()._head_idx;
if(index == -1) {
return end();
} else {
return {self()._items, self()._nodes, index};
}
}
iterator end() const { return {nullptr, nullptr, -1}; }
template <typename Key>
bool contains(Key&& key) const {
return self().contains(vm, pybind11::cast(std::forward<Key>(key)).ptr());
}
#endif
int size() const { return self().size(); }
bool empty() const { return size() == 0; }
void clear() { self().clear(); }
dict_accessor operator[] (int index) const;
dict_accessor operator[] (std::string_view) const;
dict_accessor operator[] (const handle& key) const;
};
class function : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::Function, vm->tp_function);
};
//
// class buffer : public object {
// public:
// using object::object;
//};
//
// class memory_view : public object {
// public:
// using object::object;
//};
//
class capsule : public object {
PYBIND11_REGISTER_INIT([] {
type_visitor::create<impl::capsule>(vm->builtins, "capsule", true);
});
PYBIND11_TYPE_IMPLEMENT(object, impl::capsule, handle(vm->builtins->attr("capsule"))._as<pkpy::Type>());
public:
capsule(void* ptr, void (*destructor)(void*) = nullptr) : object(create(ptr, destructor)) {}
void* data() const { return self().ptr; }
template <typename T>
T& cast() const {
return *static_cast<T*>(self().ptr);
}
};
class property : public object {
PYBIND11_TYPE_IMPLEMENT(object, pkpy::Property, vm->tp_property);
public:
#if PK_VERSION_MAJOR == 2
property(handle getter, handle setter) : object(create(getter.ptr(), setter.ptr())) {}
#else
property(handle getter, handle setter) : object(create(pkpy::Property{getter.ptr(), setter.ptr()})) {}
#endif
handle getter() const { return self().getter; }
handle setter() const { return self().setter; }
};
class args : public tuple {
PYBIND11_TYPE_IMPLEMENT(tuple, pybind11::empty, vm->tp_tuple);
};
class kwargs : public dict {
PYBIND11_TYPE_IMPLEMENT(dict, pybind11::empty, vm->tp_dict);
};
} // namespace pybind11

View File

@ -0,0 +1,201 @@
#pragma once
#include "pybind11.h"
namespace pybind11::impl {
enum op_id : int {
op_add,
op_sub,
op_mul,
op_div,
op_mod,
op_divmod,
op_pow,
op_lshift,
op_rshift,
op_and,
op_xor,
op_or,
op_neg,
op_pos,
op_abs,
op_invert,
op_int,
op_long,
op_float,
op_str,
op_cmp,
op_gt,
op_ge,
op_lt,
op_le,
op_eq,
op_ne,
op_iadd,
op_isub,
op_imul,
op_idiv,
op_imod,
op_ilshift,
op_irshift,
op_iand,
op_ixor,
op_ior,
op_complex,
op_bool,
op_nonzero,
op_repr,
op_truediv,
op_itruediv,
op_hash
};
enum op_type : int {
op_l, /* base type on left */
op_r, /* base type on right */
op_u /* unary operator */
};
struct self_t {};
const static self_t self = self_t();
/// Type for an unused type slot
struct undefined_t {};
/// Don't warn about an unused variable
inline self_t __self() { return self; }
/// base template of operator implementations
template <op_id, op_type, typename B, typename L, typename R>
struct op_impl {};
/// Operator implementation generator
template <op_id id, op_type ot, typename L, typename R>
struct op_ {
constexpr static bool op_enable_if_hook = true;
template <typename Class, typename... Extra>
void execute(Class& cl, const Extra&... extra) const {
using Base = typename Class::underlying_type;
using L_type = std::conditional_t<std::is_same<L, self_t>::value, Base, L>;
using R_type = std::conditional_t<std::is_same<R, self_t>::value, Base, R>;
using op = op_impl<id, ot, Base, L_type, R_type>;
cl.def(op::name(), &op::execute, extra...);
}
template <typename Class, typename... Extra>
void execute_cast(Class& cl, const Extra&... extra) const {
using Base = typename Class::type;
using L_type = std::conditional_t<std::is_same<L, self_t>::value, Base, L>;
using R_type = std::conditional_t<std::is_same<R, self_t>::value, Base, R>;
using op = op_impl<id, ot, Base, L_type, R_type>;
cl.def(op::name(), &op::execute_cast, extra...);
}
};
#define PYBIND11_BINARY_OPERATOR(id, rid, op, expr) \
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_l, B, L, R> { \
static char const* name() { return "__" #id "__"; } \
static auto execute(const L& l, const R& r) -> decltype(expr) { return (expr); } \
static B execute_cast(const L& l, const R& r) { return B(expr); } \
}; \
\
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_r, B, L, R> { \
static char const* name() { return "__" #rid "__"; } \
static auto execute(const R& r, const L& l) -> decltype(expr) { return (expr); } \
static B execute_cast(const R& r, const L& l) { return B(expr); } \
}; \
\
inline op_<op_##id, op_l, self_t, self_t> op(const self_t&, const self_t&) { \
return op_<op_##id, op_l, self_t, self_t>(); \
} \
\
template <typename T> \
op_<op_##id, op_l, self_t, T> op(const self_t&, const T&) { \
return op_<op_##id, op_l, self_t, T>(); \
} \
\
template <typename T> \
op_<op_##id, op_r, T, self_t> op(const T&, const self_t&) { \
return op_<op_##id, op_r, T, self_t>(); \
}
#define PYBIND11_INPLACE_OPERATOR(id, op, expr) \
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_l, B, L, R> { \
static char const* name() { return "__" #id "__"; } \
static auto execute(L& l, const R& r) -> decltype(expr) { return expr; } \
static B execute_cast(L& l, const R& r) { return B(expr); } \
}; \
\
template <typename T> \
op_<op_##id, op_l, self_t, T> op(const self_t&, const T&) { \
return op_<op_##id, op_l, self_t, T>(); \
}
#define PYBIND11_UNARY_OPERATOR(id, op, expr) \
template <typename B, typename L> \
struct op_impl<op_##id, op_u, B, L, undefined_t> { \
static char const* name() { return "__" #id "__"; } \
static auto execute(const L& l) -> decltype(expr) { return expr; } \
static B execute_cast(const L& l) { return B(expr); } \
}; \
\
inline op_<op_##id, op_u, self_t, undefined_t> op(const self_t&) { \
return op_<op_##id, op_u, self_t, undefined_t>(); \
}
PYBIND11_BINARY_OPERATOR(sub, rsub, operator-, l - r)
PYBIND11_BINARY_OPERATOR(add, radd, operator+, l + r)
PYBIND11_BINARY_OPERATOR(mul, rmul, operator*, l* r)
PYBIND11_BINARY_OPERATOR(truediv, rtruediv, operator/, l / r)
PYBIND11_BINARY_OPERATOR(mod, rmod, operator%, l % r)
PYBIND11_BINARY_OPERATOR(lshift, rlshift, operator<<, l << r)
PYBIND11_BINARY_OPERATOR(rshift, rrshift, operator>>, l >> r)
PYBIND11_BINARY_OPERATOR(and, rand, operator&, l& r)
PYBIND11_BINARY_OPERATOR(xor, rxor, operator^, l ^ r)
PYBIND11_BINARY_OPERATOR(eq, eq, operator==, l == r)
PYBIND11_BINARY_OPERATOR(ne, ne, operator!=, l != r)
PYBIND11_BINARY_OPERATOR(or, ror, operator|, l | r)
PYBIND11_BINARY_OPERATOR(gt, lt, operator>, l > r)
PYBIND11_BINARY_OPERATOR(ge, le, operator>=, l >= r)
PYBIND11_BINARY_OPERATOR(lt, gt, operator<, l < r)
PYBIND11_BINARY_OPERATOR(le, ge, operator<=, l <= r)
// PYBIND11_BINARY_OPERATOR(pow, rpow, pow, std::pow(l, r))
PYBIND11_INPLACE_OPERATOR(iadd, operator+=, l += r)
PYBIND11_INPLACE_OPERATOR(isub, operator-=, l -= r)
PYBIND11_INPLACE_OPERATOR(imul, operator*=, l *= r)
PYBIND11_INPLACE_OPERATOR(itruediv, operator/=, l /= r)
PYBIND11_INPLACE_OPERATOR(imod, operator%=, l %= r)
PYBIND11_INPLACE_OPERATOR(ilshift, operator<<=, l <<= r)
PYBIND11_INPLACE_OPERATOR(irshift, operator>>=, l >>= r)
PYBIND11_INPLACE_OPERATOR(iand, operator&=, l &= r)
PYBIND11_INPLACE_OPERATOR(ixor, operator^=, l ^= r)
PYBIND11_INPLACE_OPERATOR(ior, operator|=, l |= r)
PYBIND11_UNARY_OPERATOR(neg, operator-, -l)
PYBIND11_UNARY_OPERATOR(pos, operator+, +l)
// WARNING: This usage of `abs` should only be done for existing STL overloads.
// Adding overloads directly in to the `std::` namespace is advised against:
// https://en.cppreference.com/w/cpp/language/extending_std
// PYBIND11_UNARY_OPERATOR(abs, abs, std::abs(l))
// PYBIND11_UNARY_OPERATOR(hash, hash, std::hash<L>()(l))
// PYBIND11_UNARY_OPERATOR(invert, operator~, (~l))
// PYBIND11_UNARY_OPERATOR(bool, operator!, !!l)
// PYBIND11_UNARY_OPERATOR(int, int_, (int)l)
// PYBIND11_UNARY_OPERATOR(float, float_, (double)l)
#undef PYBIND11_BINARY_OPERATOR
#undef PYBIND11_INPLACE_OPERATOR
#undef PYBIND11_UNARY_OPERATOR
} // namespace pybind11::impl
namespace pybind11 {
using impl::self;
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "internal/class.h"
namespace pybind11 {
namespace literals {
inline arg operator""_a (const char* c, size_t) { return arg(c); }
} // namespace literals
struct scoped_interpreter {
scoped_interpreter() { interpreter::initialize(); }
~scoped_interpreter() { interpreter::finalize(); }
};
} // namespace pybind11
// namespace pybind11
#undef PYBIND11_TYPE_IMPLEMENT
#undef PYBIND11_REGISTER_INIT

144
include/pybind11/stl.h Normal file
View File

@ -0,0 +1,144 @@
#include "pybind11.h"
#include <array>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <map>
#include <unordered_map>
namespace pybind11::impl {
template <typename T, std::size_t N>
struct type_caster<std::array<T, N>> {
struct wrapper {
std::array<T, N> container = {};
operator std::array<T, N>&& () { return std::move(container); }
};
wrapper value;
bool load(const handle& src, bool convert) {
if(!isinstance<list>(src)) { return false; }
auto list = src.cast<pybind11::list>();
if(list.size() != N) { return false; }
for(std::size_t i = 0; i < N; ++i) {
type_caster<T> caster;
if(!caster.load(list[i], convert)) { return false; }
value.container[i] = caster.value;
}
return true;
}
template <typename U>
static handle cast(U&& src, return_value_policy policy, handle parent) {
auto list = pybind11::list();
for(auto& item: src) {
list.append(pybind11::cast(item, policy, parent));
}
return list;
}
};
template <typename T>
constexpr bool is_py_list_like_v = false;
template <typename T, typename Allocator>
constexpr bool is_py_list_like_v<std::vector<T, Allocator>> = true;
template <typename T, typename Allocator>
constexpr bool is_py_list_like_v<std::list<T, Allocator>> = true;
template <typename T, typename Allocator>
constexpr bool is_py_list_like_v<std::deque<T, Allocator>> = true;
template <typename T>
struct type_caster<T, std::enable_if_t<is_py_list_like_v<T>>> {
struct wrapper {
T container;
operator T&& () { return std::move(container); }
};
wrapper value;
bool load(const handle& src, bool convert) {
if(!isinstance<list>(src)) { return false; }
auto list = src.cast<pybind11::list>();
for(auto item: list) {
type_caster<typename T::value_type> caster;
if(!caster.load(item, convert)) { return false; }
value.container.push_back(caster.value);
}
return true;
}
template <typename U>
static handle cast(U&& src, return_value_policy policy, handle parent) {
auto list = pybind11::list();
for(auto& item: src) {
list.append(pybind11::cast(item, policy, parent));
}
return list;
}
};
template <typename T>
constexpr bool is_py_map_like_v = false;
template <typename Key, typename T, typename Compare, typename Allocator>
constexpr bool is_py_map_like_v<std::map<Key, T, Compare, Allocator>> = true;
template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
constexpr bool is_py_map_like_v<std::unordered_map<Key, T, Hash, KeyEqual, Allocator>> = true;
template <typename T>
struct type_caster<T, std::enable_if_t<is_py_map_like_v<T>>> {
struct wrapper {
T container;
operator T&& () { return std::move(container); }
};
wrapper value;
bool load(const handle& src, bool convert) {
if(!isinstance<dict>(src)) { return false; }
auto dict = src.cast<pybind11::dict>();
for(auto item: dict) {
type_caster<typename T::key_type> key_caster;
if(!key_caster.load(item.first, convert)) { return false; }
type_caster<typename T::mapped_type> value_caster;
if(!value_caster.load(item.second, convert)) { return false; }
value.container.try_emplace(key_caster.value, value_caster.value);
}
return true;
}
template <typename U>
static handle cast(U&& src, return_value_policy policy, handle parent) {
auto dict = pybind11::dict();
for(auto& [key, value]: src) {
dict[pybind11::cast(key, policy, parent)] = pybind11::cast(value, policy, parent);
}
return dict;
}
};
} // namespace pybind11::impl

View File

@ -77,8 +77,8 @@ class complex:
def __pow__(self, other: int | float):
if type(other) in (int, float):
return complex(self.__abs__() ** other * math.cos(other * phase(self)),
self.__abs__() ** other * math.sin(other * phase(self)))
return complex(abs(self) ** other * math.cos(other * phase(self)),
abs(self) ** other * math.sin(other * phase(self)))
return NotImplemented
def __abs__(self) -> float:
@ -97,7 +97,7 @@ def phase(z: complex):
return math.atan2(z.imag, z.real)
def polar(z: complex):
return z.__abs__(), phase(z)
return abs(z), phase(z)
def rect(r: float, phi: float):
return r * math.cos(phi) + r * math.sin(phi) * 1j
@ -108,7 +108,7 @@ def exp(z: complex):
return math.exp(z.real) * rect(1, z.imag)
def log(z: complex, base=2.718281828459045):
return math.log(z.__abs__(), base) + phase(z) * 1j
return math.log(abs(z), base) + phase(z) * 1j
def log10(z: complex):
return log(z, 10)

File diff suppressed because one or more lines are too long

View File

@ -84,7 +84,7 @@ void add_module_sys(VM* vm){
vm->bind_func(stderr_, "write", 1, [](VM* vm, ArgsView args) {
Str& s = CAST(Str&, args[0]);
vm->_stderr(s.data, s.size);
vm->stderr_write(s);
return vm->None;
});
}

View File

@ -158,8 +158,7 @@ void __init_builtins(VM* _vm) {
_vm->bind_func(_vm->builtins, "abs", 1, [](VM* vm, ArgsView args) {
if(is_int(args[0])) return VAR(std::abs(_CAST(i64, args[0])));
if(is_float(args[0])) return VAR(std::abs(_CAST(f64, args[0])));
vm->TypeError("bad operand type for abs()");
return vm->None;
return vm->call_method(args[0], __abs__);
});
_vm->bind(_vm->builtins, "max(*args, key=None)", [](VM* vm, ArgsView args){

View File

@ -592,6 +592,7 @@ const StrName __package__ = StrName::get("__package__");
const StrName __path__ = StrName::get("__path__");
const StrName __class__ = StrName::get("__class__");
const StrName __missing__ = StrName::get("__missing__");
const StrName __abs__ = StrName::get("__abs__");
const StrName pk_id_add = StrName::get("add");
const StrName pk_id_set = StrName::get("set");

View File

@ -518,10 +518,11 @@ i64 VM::py_hash(PyVar obj){
}
PyVar VM::__py_exec_internal(const CodeObject_& code, PyVar globals, PyVar locals){
Frame* frame = &vm->callstack.top();
Frame* frame = nullptr;
if(!callstack.empty()) frame = &callstack.top();
// fast path
if(globals == vm->None && locals == vm->None){
if(frame && globals == vm->None && locals == vm->None){
return vm->_exec(code.get(), frame->_module, frame->_callable, frame->_locals);
}
@ -534,7 +535,7 @@ PyVar VM::__py_exec_internal(const CodeObject_& code, PyVar globals, PyVar local
Dict* locals_dict = nullptr;
if(globals == vm->None){
globals_obj = frame->_module;
globals_obj = frame ? frame->_module : _main;
}else{
if(is_type(globals, VM::tp_mappingproxy)){
globals_obj = PK_OBJ_GET(MappingProxy, globals).obj;
@ -1081,8 +1082,10 @@ PyVar VM::vectorcall(int ARGC, int KWARGC, bool op_call){
for(int j=0; j<co_nlocals; j++) _base[j] = buffer[j];
ret = f.call(vm, ArgsView(s_data._sp - co_nlocals, s_data._sp));
}else{
if(f.argc != -1){
if(KWARGC != 0) TypeError("old-style native_func does not accept keyword arguments");
f.check_size(this, args);
}
ret = f.call(this, args);
}
s_data.reset(p0);
@ -1133,7 +1136,7 @@ PyVar VM::vectorcall(int ARGC, int KWARGC, bool op_call){
p1[-(ARGC + 2)] = call_f;
p1[-(ARGC + 1)] = self;
// [call_f, self, args..., kwargs...]
return vectorcall(ARGC, KWARGC, false);
return vectorcall(ARGC, KWARGC, op_call);
}
TypeError(_type_name(vm, callable_t).escape() + " object is not callable");
PK_UNREACHABLE()