mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-20 11:30:18 +00:00
...
This commit is contained in:
parent
d3fd75d0be
commit
96a760dfd8
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
icon: log
|
icon: log
|
||||||
title: 'Upgrade to v2.0.0'
|
title: 'Upgrade to v2.0'
|
||||||
order: 25
|
order: 25
|
||||||
---
|
---
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
title: Call
|
|
||||||
icon: dot
|
|
||||||
order: 6
|
|
||||||
---
|
|
||||||
|
|
||||||
### `bool pkpy_vectorcall(pkpy_vm*, int argc)`
|
|
||||||
|
|
||||||
Wraps `vm->vectorcall(argc)`. This function is used to call a function with a fixed number of arguments. The arguments are popped from the stack. The return value is pushed onto the stack.
|
|
||||||
|
|
||||||
1. First push the function to call.
|
|
||||||
2. Push `self` argument if it is a method call. Otherwise, call `pkpy_push_null`.
|
|
||||||
3. Push arguments from left to right.
|
|
||||||
|
|
||||||
!!!
|
|
||||||
Unlike lua, a python function always returns a value.
|
|
||||||
If the function returns `void`, it will push `None` onto the stack.
|
|
||||||
You can call `pkpy_pop_top` to discard the return value.
|
|
||||||
!!!
|
|
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
title: Error Handling
|
|
||||||
icon: dot
|
|
||||||
order: 5
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `bool pkpy_clear_error(pkpy_vm*, char** message)`
|
|
||||||
|
|
||||||
+ If a method returns false, call the `pkpy_clear_error` method to check the error and clear it
|
|
||||||
+ If `pkpy_clear_error` returns false, it means that no error was set, and it takes no action
|
|
||||||
+ If `pkpy_clear_error` returns true, it means there was an error and it was cleared. It will provide a string summary of the error in the message parameter if it is not `NULL`.
|
|
||||||
|
|
||||||
!!!
|
|
||||||
You are responsible for freeing `message`.
|
|
||||||
!!!
|
|
||||||
|
|
||||||
#### `bool pkpy_check_error(pkpy_vm*)`
|
|
||||||
|
|
||||||
Return true if the vm is currently in an error state.
|
|
||||||
|
|
||||||
#### `bool pkpy_error(pkpy_vm*, const char* name, pkpy_CString message)`
|
|
||||||
|
|
||||||
Set the error state of the vm. It is almost equivalent to `raise` in python.
|
|
@ -1,3 +1,3 @@
|
|||||||
label: C-API
|
label: C-API
|
||||||
icon: code
|
icon: code
|
||||||
order: 1
|
order: 15
|
@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
title: Specials
|
|
||||||
icon: dot
|
|
||||||
order: 6
|
|
||||||
---
|
|
||||||
|
|
||||||
+ `void pkpy_free(void* p)`
|
|
||||||
|
|
||||||
Wraps `free(p)` in C++.
|
|
||||||
|
|
||||||
+ `pkpy_CString pkpy_string(const char*)`
|
|
||||||
|
|
||||||
Construct a `pkpy_CString` from a null-terminated C string.
|
|
||||||
|
|
||||||
+ `pkpy_CName pkpy_name(const char*)`
|
|
||||||
|
|
||||||
Construct a `pkpy_CName` from a null-terminated C string. You should cache the result of this function if you are going to use it multiple times.
|
|
||||||
|
|
||||||
+ `pkpy_CString pkpy_name_to_string(pkpy_CName)`
|
|
||||||
|
|
||||||
Convert a `pkpy_CName` to a `pkpy_CString`.
|
|
295
docs/bindings.md
295
docs/bindings.md
@ -6,294 +6,13 @@ order: 18
|
|||||||
|
|
||||||
In order to use a C/C++ library in python, you need to write bindings for it.
|
In order to use a C/C++ library in python, you need to write bindings for it.
|
||||||
|
|
||||||
pkpy uses an universal signature to wrap a function pointer as a python function or method that can be called in python code, i.e `NativeFuncC`.
|
pkpy uses an universal signature to wrap a C function pointer as a python function or method, i.e `py_CFunction`.
|
||||||
|
|
||||||
```cpp
|
```c
|
||||||
typedef PyVar (*NativeFuncC)(VM*, ArgsView);
|
typedef bool (*py_CFunction)(int argc, py_Ref argv);
|
||||||
```
|
```
|
||||||
+ The first argument is the pointer of `VM` instance.
|
+ `argc` is the number of arguments passed to the function.
|
||||||
+ The second argument is an array-like object indicates the arguments list. You can use `[]` operator to get the element and call `size()` to get the length of the array.
|
+ `argv` is the pointer to the first argument.
|
||||||
+ The return value is a `PyVar`, which should not be `nullptr`. If there is no return value, return `vm->None`.
|
|
||||||
|
|
||||||
## Bind a function or method
|
If successful, the function should return `true` and set the return value in `py_retval()`. In case there is no return value, you should use `py_newnone(py_retval())`.
|
||||||
|
If an error occurs, the function should raise an exception and return `false`.
|
||||||
Use `vm->bind` to bind a function or method.
|
|
||||||
|
|
||||||
+ `PyVar bind(PyVar, const char* sig, NativeFuncC)`
|
|
||||||
+ `PyVar bind(PyVar, const char* sig, const char* docstring, NativeFuncC)`
|
|
||||||
|
|
||||||
```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);
|
|
||||||
});
|
|
||||||
|
|
||||||
// or you can provide a docstring
|
|
||||||
vm->bind(obj,
|
|
||||||
"add(a: int, b: int) -> int",
|
|
||||||
"add two integers", [](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);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### How to capture something
|
|
||||||
|
|
||||||
By default, the lambda being bound is a C function pointer,
|
|
||||||
you cannot capture anything! The following example does not compile.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int x = 1;
|
|
||||||
vm->bind(obj, "f() -> int", [x](VM* vm, ArgsView args){
|
|
||||||
// error: cannot capture 'x'
|
|
||||||
return py_var(vm, x);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
I do not encourage you to capture something in a lambda being bound
|
|
||||||
because:
|
|
||||||
1. Captured lambda runs slower and causes "code-bloat".
|
|
||||||
2. Captured values are unsafe, especially for `PyVar` as they could leak by accident.
|
|
||||||
|
|
||||||
However, there are 3 ways to capture something when you really need to.
|
|
||||||
The most safe and elegant way is to subclass `VM` and add a member variable.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
class YourVM : public VM{
|
|
||||||
public:
|
|
||||||
int x;
|
|
||||||
YourVM() : VM() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(){
|
|
||||||
YourVM* vm = new YourVM();
|
|
||||||
vm->x = 1;
|
|
||||||
vm->bind(obj, "f() -> int", [](VM* _vm, ArgsView args){
|
|
||||||
// do a static_cast and you can get any extra members of YourVM
|
|
||||||
YourVM* vm = static_cast<YourVM*>(_vm);
|
|
||||||
return py_var(vm, vm->x);
|
|
||||||
});
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The 2nd way is to use `vm->bind`'s last parameter `userdata`, you can store an `pkpy::any` object.
|
|
||||||
And use `lambda_get_userdata<T>(args.begin())` to get it inside the lambda body.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int x = 1;
|
|
||||||
vm->bind(obj, "f() -> int", [](VM* vm, ArgsView args){
|
|
||||||
// get the userdata
|
|
||||||
int x = lambda_get_userdata<int>(args.begin());
|
|
||||||
return py_var(vm, x);
|
|
||||||
}, x); // capture x
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bind a class or struct
|
|
||||||
|
|
||||||
Assume you have a struct `Point` declared as follows.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
struct Point{
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can create a `test` module and use `vm->register_user_class<>` to bind the class to the test module.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int main(){
|
|
||||||
VM* vm = new VM();
|
|
||||||
PyObject* mod = vm->new_module("test");
|
|
||||||
vm->register_user_class<Point>(mod, "Point",
|
|
||||||
[](VM* vm, PyVar mod, PyVar type){
|
|
||||||
// wrap field x
|
|
||||||
vm->bind_field(type, "x", &Point::x);
|
|
||||||
// wrap field y
|
|
||||||
vm->bind_field(type, "y", &Point::y);
|
|
||||||
|
|
||||||
// __init__ method
|
|
||||||
vm->bind(type, "__init__(self, x, y)", [](VM* vm, ArgsView args){
|
|
||||||
Point& self = _py_cast<Point&>(vm, args[0]);
|
|
||||||
self.x = py_cast<int>(vm, args[1]);
|
|
||||||
self.y = py_cast<int>(vm, args[2]);
|
|
||||||
return vm->None;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// use the Point class
|
|
||||||
vm->exec("import test");
|
|
||||||
vm->exec("a = test.Point(1, 2)");
|
|
||||||
vm->exec("print(a.x)"); // 1
|
|
||||||
vm->exec("print(a.y)"); // 2
|
|
||||||
|
|
||||||
delete vm;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Handle gc for container types
|
|
||||||
|
|
||||||
If your custom type stores `PyVar` in its fields, you need to handle gc for them.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
struct Container{
|
|
||||||
PyVar a;
|
|
||||||
std::vector<PyVar> b;
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Add a magic method `_gc_mark(VM*) const` to your custom type.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
struct Container{
|
|
||||||
PyVar a;
|
|
||||||
std::vector<PyVar> b;
|
|
||||||
// ...
|
|
||||||
|
|
||||||
void _gc_mark(VM* vm) const{
|
|
||||||
// mark a
|
|
||||||
vm->obj_gc_mark(a);
|
|
||||||
|
|
||||||
// mark elements in b
|
|
||||||
for(PyVar obj : b){
|
|
||||||
vm->obj_gc_mark(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For global objects, use the callback in `vm->heap`.
|
|
||||||
```cpp
|
|
||||||
void (*_gc_marker_ex)(VM*) = nullptr;
|
|
||||||
```
|
|
||||||
It will be invoked before a GC starts. So you can mark objects inside the callback to keep them alive.
|
|
||||||
|
|
||||||
## Others
|
|
||||||
|
|
||||||
For some magic methods, we provide specialized binding function.
|
|
||||||
They do not take universal function pointer as argument.
|
|
||||||
You need to provide the detailed `Type` object and the corresponding function pointer.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
PyVar f_add(VM* vm, PyVar lhs, PyVar rhs){
|
|
||||||
int a = py_cast<int>(vm, lhs);
|
|
||||||
int b = py_cast<int>(vm, rhs);
|
|
||||||
return py_var(vm, a + b);
|
|
||||||
}
|
|
||||||
|
|
||||||
vm->bind__add__(vm->tp_int, f_add);
|
|
||||||
```
|
|
||||||
|
|
||||||
This specialized binding function has optimizations and result in better performance when calling from python code.
|
|
||||||
|
|
||||||
For example, `vm->bind__add__` is preferred over `vm->bind_func(type, "__add__", 2, f_add)`.
|
|
||||||
|
|
||||||
|
|
||||||
## Further reading
|
|
||||||
|
|
||||||
See [random.cpp](https://github.com/pocketpy/pocketpy/blob/main/src/random.cpp) for an example used by `random` module.
|
|
||||||
|
|
||||||
See [collections.cpp](https://github.com/pocketpy/pocketpy/blob/main/src/collections.cpp) for a modern implementation of `collections.deque`.
|
|
||||||
|
|
||||||
## Reuse Lua bindings
|
|
||||||
|
|
||||||
pkpy provides a lua bridge to reuse lua bindings.
|
|
||||||
It allows you to run lua code and call lua functions in python
|
|
||||||
by embedding a lua virtual machine.
|
|
||||||
|
|
||||||
Add `lua_bridge.hpp` and `lua_bridge.cpp` in [3rd/lua_bridge](https://github.com/pocketpy/pocketpy/tree/main/3rd/lua_bridge) to your project.
|
|
||||||
Make sure `lua.h`, `lualib.h` and `lauxlib.h` are in your include path
|
|
||||||
because `lua_bridge.hpp` needs them.
|
|
||||||
|
|
||||||
The lua bridge is based on lua 5.1.5 for maximum compatibility.
|
|
||||||
lua 5.2 or higher should also work.
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
Use `initialize_lua_bridge(VM*, lua_State*)` to initialize the lua bridge.
|
|
||||||
This creates a new module `lua` in your python virtual machine.
|
|
||||||
|
|
||||||
You can use `lua.dostring` to execute lua code and get the result.
|
|
||||||
And use `lua.Table()` to create a lua table.
|
|
||||||
A `lua.Table` instance in python is a dict-like object which provides a bunch of
|
|
||||||
magic methods to access the underlying lua table.
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Table:
|
|
||||||
def keys(self) -> list:
|
|
||||||
"""Return a list of keys in the table."""
|
|
||||||
|
|
||||||
def values(self) -> list:
|
|
||||||
"""Return a list of values in the table."""
|
|
||||||
|
|
||||||
def items(self) -> list[tuple]:
|
|
||||||
"""Return a list of (key, value) pairs in the table."""
|
|
||||||
|
|
||||||
def __len__(self) -> int:
|
|
||||||
"""Return the length of the table."""
|
|
||||||
|
|
||||||
def __contains__(self, key) -> bool:
|
|
||||||
"""Return True if the table contains the key."""
|
|
||||||
|
|
||||||
def __getitem__(self, key): ...
|
|
||||||
def __setitem__(self, key, value): ...
|
|
||||||
def __delitem__(self, key): ...
|
|
||||||
def __getattr__(self, key): ...
|
|
||||||
def __setattr__(self, key, value): ...
|
|
||||||
def __delattr__(self, key): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Only basic types can be passed between python and lua.
|
|
||||||
The following table shows the type mapping.
|
|
||||||
If you pass an unsupported type, an exception will be raised.
|
|
||||||
|
|
||||||
| Python type | Lua type | Allow create in Python? | Reference? |
|
|
||||||
| ----------- | -------- | ---------------------- | --------- |
|
|
||||||
| `None` | `nil` | YES | NO |
|
|
||||||
| `bool` | `boolean` | YES | NO |
|
|
||||||
| `int` | `number` | YES | NO |
|
|
||||||
| `float` | `number` | YES | NO |
|
|
||||||
| `str` | `string` | YES | NO |
|
|
||||||
| `tuple` | `table` | YES | NO |
|
|
||||||
| `list` | `table` | YES | NO |
|
|
||||||
| `dict` | `table` | YES | NO |
|
|
||||||
| `lua.Table` | `table` | YES | YES |
|
|
||||||
| `lua.Function`| `function`| NO | YES |
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```cpp
|
|
||||||
#include "lua_bridge.hpp"
|
|
||||||
|
|
||||||
using namespace pkpy;
|
|
||||||
|
|
||||||
int main(){
|
|
||||||
VM* vm = new VM();
|
|
||||||
|
|
||||||
// create lua state
|
|
||||||
lua_State* L = luaL_newstate();
|
|
||||||
luaL_openlibs(L);
|
|
||||||
|
|
||||||
// initialize lua bridge
|
|
||||||
initialize_lua_bridge(vm, L);
|
|
||||||
|
|
||||||
// dostring to get _G
|
|
||||||
vm->exec("import lua");
|
|
||||||
vm->exec("g = lua.dostring('return _G')");
|
|
||||||
|
|
||||||
// create a table
|
|
||||||
vm->exec("t = lua.Table()");
|
|
||||||
vm->exec("t.a = 1");
|
|
||||||
vm->exec("t.b = 2");
|
|
||||||
|
|
||||||
// call lua function
|
|
||||||
vm->exec("g.print(t.a + t.b)"); // 3
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
@ -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(TopLevelException 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
|
|
||||||
PyObject* mod = vm->new_module("test");
|
|
||||||
vm->setattr(mod, "pi", py_var(vm, 3.14));
|
|
||||||
```
|
|
@ -1,106 +0,0 @@
|
|||||||
---
|
|
||||||
icon: book
|
|
||||||
order: -5
|
|
||||||
label: Coding Style Guide
|
|
||||||
---
|
|
||||||
|
|
||||||
# Coding Style Guide
|
|
||||||
|
|
||||||
|
|
||||||
## For Python
|
|
||||||
|
|
||||||
Use [PEP-8](https://www.python.org/dev/peps/pep-0008/) as the coding style guide.
|
|
||||||
|
|
||||||
## For C++
|
|
||||||
|
|
||||||
### Naming rules
|
|
||||||
|
|
||||||
For class names, always use **PascalCase**
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// Correct
|
|
||||||
class FooBar {};
|
|
||||||
|
|
||||||
// Wrong
|
|
||||||
class fooBar {};
|
|
||||||
class foo_bar {};
|
|
||||||
```
|
|
||||||
|
|
||||||
For function and methods, use **snake_case**
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// Correct
|
|
||||||
int test_func(int x) { return x+1; }
|
|
||||||
|
|
||||||
// Wrong
|
|
||||||
int TestFunc(int x) { return x+1; }
|
|
||||||
int testFunc(int x) { return x+1; }
|
|
||||||
```
|
|
||||||
|
|
||||||
For special python objects, use the same name as in python.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
auto x = vm->None;
|
|
||||||
vm->SyntaxError(...);
|
|
||||||
vm->TypeError(...);
|
|
||||||
vm->call(obj, __repr__);
|
|
||||||
```
|
|
||||||
|
|
||||||
For global constants, use **k** prefix with **PascalCase**
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
const int kMaxCount = 10;
|
|
||||||
const float kMinValue = 1.0;
|
|
||||||
```
|
|
||||||
|
|
||||||
For macros, use **SNAKE_CASE**
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define FOO_BAR 1
|
|
||||||
#define TEST(x) x+1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Access control
|
|
||||||
|
|
||||||
Please use python style access control.
|
|
||||||
|
|
||||||
We do not recommend to use C++ keywords such as `private` or `public` to achieve access control. Also do not write any trivial setter/getter.
|
|
||||||
|
|
||||||
Use a single `_` as prefix to indicate a function or variable is for internal use.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
class FooBar {
|
|
||||||
public:
|
|
||||||
int _count;
|
|
||||||
int inc() { _count+=1; }
|
|
||||||
void _clear() { _count=0; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`_` prefix is just a warning to remind you to use such members carefully.
|
|
||||||
|
|
||||||
It does not forbid users to access internal members.
|
|
||||||
|
|
||||||
### Use compact style
|
|
||||||
|
|
||||||
Try to make the code compact if it does not affect readability.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// Correct
|
|
||||||
if(x == 1) break;
|
|
||||||
|
|
||||||
// Wrong
|
|
||||||
if(x == 1){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### For `std::shared_ptr<T>`
|
|
||||||
|
|
||||||
Use a `_` suffix to indicate a type is a shared pointer.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
using CodeObject_ = std::shared_ptr<CodeObject>;
|
|
||||||
CodeObject_ co = std::make_shared<CodeObject>();
|
|
||||||
```
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
icon: dot
|
|
||||||
title: Arbitrary Sized Integers
|
|
||||||
---
|
|
||||||
|
|
||||||
Unlike cpython, pkpy's `int` is of limited precision (64-bit).
|
|
||||||
|
|
||||||
For arbitrary sized integers, we provide a builtin `long` type, just like python2's `long`.
|
|
||||||
`long` is implemented via pure python in [_long.py](https://github.com/pocketpy/pocketpy/blob/main/python/_long.py).
|
|
||||||
|
|
||||||
### Create a long object
|
|
||||||
|
|
||||||
You can use `L` suffix to create a `long` literal from a decimal literal.
|
|
||||||
Also, you can use `long()` function to create a `long` object from a `int` object or a `str` object.
|
|
||||||
|
|
||||||
```python
|
|
||||||
a = 1000L
|
|
||||||
b = long(1000)
|
|
||||||
c = long('1000')
|
|
||||||
assert a == b == c
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
a = 2L # use `L` suffix to create a `long` object
|
|
||||||
print(a ** 1000)
|
|
||||||
# 10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376L
|
|
||||||
```
|
|
@ -1,2 +1,2 @@
|
|||||||
order: 19
|
order: 0
|
||||||
label: "GSoC"
|
label: "GSoC"
|
@ -5,12 +5,11 @@ label: Welcome
|
|||||||
|
|
||||||
# Welcome to pocketpy
|
# Welcome to pocketpy
|
||||||
|
|
||||||
pkpy is a lightweight(~15K LOC) Python interpreter for game scripting, built on C++17 with STL.
|
pkpy is a lightweight(~15K LOC) Python 3.x interpreter for game scripting, written in C11.
|
||||||
|
|
||||||
It aims to be an alternative to lua for game scripting, with elegant syntax, powerful features and competitive performance.
|
It aims to be an alternative to lua for game scripting, with elegant syntax, powerful features and competitive performance.
|
||||||
pkpy is extremely easy to embed via a single header file `pocketpy.h`, without external dependencies.
|
pkpy is extremely easy to embed via a single header file `pocketpy.h`, without external dependencies.
|
||||||
|
|
||||||
> **Caution**: pocketpy should not be your first C++ project. Please learn C++ programming, compiling, linking, and debugging before working with pocketpy. There are many resources for this on the net.
|
|
||||||
|
|
||||||
## What it looks like
|
## What it looks like
|
||||||
|
|
||||||
|
@ -19,11 +19,6 @@ In your CMakelists.txt, add the following lines:
|
|||||||
```cmake
|
```cmake
|
||||||
add_subdirectory(pocketpy)
|
add_subdirectory(pocketpy)
|
||||||
target_link_libraries(<your_target> pocketpy)
|
target_link_libraries(<your_target> pocketpy)
|
||||||
|
|
||||||
if(EMSCRIPTEN)
|
|
||||||
# exceptions must be enabled for emscripten
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fexceptions")
|
|
||||||
endif()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See [CMakeLists.txt](https://github.com/pocketpy/pocketpy/blob/main/CMakeLists.txt) for details.
|
See [CMakeLists.txt](https://github.com/pocketpy/pocketpy/blob/main/CMakeLists.txt) for details.
|
||||||
@ -34,14 +29,9 @@ It is safe to use `main` branch in production if CI badge is green.
|
|||||||
|
|
||||||
To compile it with your project, these flags must be set:
|
To compile it with your project, these flags must be set:
|
||||||
|
|
||||||
+ `--std=c++17` flag must be set
|
+ `--std=c11` flag must be set
|
||||||
+ RTTI must be enabled
|
|
||||||
+ Exception must be enabled
|
|
||||||
+ For MSVC, `/utf-8` flag must be set
|
+ For MSVC, `/utf-8` flag must be set
|
||||||
|
|
||||||
For emscripten, you must enable exceptions to make pocketpy work properly.
|
|
||||||
See https://emscripten.org/docs/porting/exceptions.html.
|
|
||||||
|
|
||||||
### Get prebuilt binaries
|
### Get prebuilt binaries
|
||||||
|
|
||||||
We have prebuilt binaries,
|
We have prebuilt binaries,
|
||||||
@ -77,41 +67,64 @@ You can download an artifact there which contains the following files.
|
|||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```cpp
|
```c
|
||||||
#include "pocketpy.h"
|
#include "pocketpy.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
using namespace pkpy;
|
static bool int_add(int argc, py_Ref argv) {
|
||||||
|
PY_CHECK_ARGC(2);
|
||||||
|
PY_CHECK_ARG_TYPE(0, tp_int);
|
||||||
|
PY_CHECK_ARG_TYPE(1, tp_int);
|
||||||
|
py_i64 a = py_toint(py_arg(0));
|
||||||
|
py_i64 b = py_toint(py_arg(1));
|
||||||
|
py_newint(py_retval(), a + b);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int main(){
|
int main() {
|
||||||
// Create a virtual machine
|
// Initialize pocketpy
|
||||||
VM* vm = new VM();
|
py_initialize();
|
||||||
|
|
||||||
// Hello world!
|
// Hello world!
|
||||||
vm->exec("print('Hello world!')");
|
bool ok = py_exec("print('Hello world!')", "<string>", EXEC_MODE, NULL);
|
||||||
|
if(!ok) goto __ERROR;
|
||||||
|
|
||||||
// Create a list
|
// Create a list: [1, 2, 3]
|
||||||
vm->exec("a = [1, 2, 3]");
|
py_Ref r0 = py_getreg(0);
|
||||||
|
py_newlistn(r0, 3);
|
||||||
|
py_newint(py_list_getitem(r0, 0), 1);
|
||||||
|
py_newint(py_list_getitem(r0, 1), 2);
|
||||||
|
py_newint(py_list_getitem(r0, 2), 3);
|
||||||
|
|
||||||
// Eval the sum of the list
|
// Eval the sum of the list
|
||||||
PyVar result = vm->eval("sum(a)");
|
py_Ref f_sum = py_getbuiltin(py_name("sum"));
|
||||||
std::cout << "Sum of the list: "<< py_cast<int>(vm, result) << std::endl; // 6
|
py_push(f_sum);
|
||||||
|
py_pushnil();
|
||||||
|
py_push(r0);
|
||||||
|
ok = py_vectorcall(1, 0);
|
||||||
|
if(!ok) goto __ERROR;
|
||||||
|
|
||||||
// Bindings
|
printf("Sum of the list: %d\n", (int)py_toint(py_retval())); // 6
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Call the function
|
// Bind native `int_add` as a global variable
|
||||||
PyVar f_add = vm->_main->attr("add");
|
py_newnativefunc(r0, int_add);
|
||||||
result = vm->call(f_add, py_var(vm, 3), py_var(vm, 7));
|
py_setglobal(py_name("add"), r0);
|
||||||
std::cout << "Sum of 2 variables: "<< py_cast<int>(vm, result) << std::endl; // 10
|
|
||||||
|
|
||||||
// Dispose the virtual machine
|
// Call `add` in python
|
||||||
delete vm;
|
ok = py_exec("add(3, 7)", "<string>", EVAL_MODE, NULL);
|
||||||
|
if(!ok) goto __ERROR;
|
||||||
|
|
||||||
|
py_i64 res = py_toint(py_retval());
|
||||||
|
printf("Sum of 2 variables: %d\n", (int)res); // 10
|
||||||
|
|
||||||
|
py_finalize();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
__ERROR:
|
||||||
|
py_printexc();
|
||||||
|
py_finalize();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Overview
|
### Overview
|
||||||
|
Loading…
x
Reference in New Issue
Block a user