Compare commits

...

7 Commits

Author SHA1 Message Date
blueloveTH
145b36b677 ... 2024-08-12 00:05:02 +08:00
blueloveTH
9d0d4d1841 ... 2024-08-11 23:59:48 +08:00
blueloveTH
26c76960e9 ... 2024-08-11 23:53:57 +08:00
blueloveTH
f22dda3125 ... 2024-08-11 23:52:41 +08:00
blueloveTH
2de9efc0e9 ... 2024-08-11 23:47:57 +08:00
blueloveTH
96a760dfd8 ... 2024-08-11 23:25:58 +08:00
blueloveTH
d3fd75d0be ... 2024-08-11 23:00:33 +08:00
32 changed files with 386 additions and 2115 deletions

View File

@ -33,9 +33,9 @@ pkpy is a lightweight(~15K LOC) Python 3.x interpreter for game scripting, writt
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.
<!-- 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++ -->
Please see https://pocketpy.dev for details and try the following resources.
+ [Live Python Demo](https://pocketpy.dev/static/web/): Run Python code in your browser
<!-- + [Live C++ Examples](https://pocketpy.github.io/examples/): Common usage of pkpy in C++ -->
## Supported Platforms

9
docs/2_0.md Normal file
View File

@ -0,0 +1,9 @@
---
icon: log
title: 'Upgrade to v2.0'
order: 25
---
pkpy v2.0 is a C11 project instead of C++17. All your existing code for v1.x won't work anymore.
We provide two API sets for v2.0, C-API and pybind11 API (C\+\+17). If you are a C user, use the C-API. If you are a C\+\+ user, use the pybind11 API.

View File

@ -1,222 +0,0 @@
---
icon: log
title: 'Upgrade to v2.0.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, PyObject* mod, PyObject* type){
// do bindings here
}
};
int main(){
VM* vm = new VM();
PyObject* 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();
PyObject* 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, PyObject* mod, PyObject* type){
// do bindings here (if you like the intrusive way)
}
};
int main(){
VM* vm = new VM();
PyObject* 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/common/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.
## Catching exceptions
Now you need to catch `TopLevelException` instead of `Exception`.

View File

@ -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.
!!!

View File

@ -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.

View File

@ -1,3 +1,3 @@
label: C-API
icon: code
order: 1
order: 15

View File

@ -4,36 +4,4 @@ icon: dot
order: 10
---
### What C-API is for
If your target platform does not support C++17. You can compile pkpy into a static library and use its exported C-APIs.
Our C-APIs take a lot of inspiration from the lua C-APIs.
Methods return a `bool` indicating if the operation succeeded or not.
Special thanks for [@koltenpearson](https://github.com/koltenpearson)'s contribution.
!!!
C-APIs are always stable and backward compatible.
!!!
### Basic functions
+ `pkpy_vm* pkpy_new_vm(bool enable_os)`
Wraps `new VM(enable_os)` in C++.
+ `void pkpy_delete_vm(pkpy_vm*)`
Wraps `delete vm` in C++.
+ `bool pkpy_exec(pkpy_vm*, const char* source)`
Wraps `vm->exec`. Execute a string of source code.
+ `bool pkpy_exec_2(pkpy_vm*, const char* source, const char* filename, int mode, const char* module)`
Wraps `vm->exec_2`. Execute a string of source code with more options.
+ `void pkpy_set_main_argv(pkpy_vm*, int argc, char** argv)`
Wraps `vm->set_main_argv`. Set the `sys.argv` before executing scripts.
TBA

View File

@ -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`.

View File

@ -1,169 +0,0 @@
---
title: Stack Manipulation
icon: dot
order: 8
---
### Basic manipulation
+ `bool pkpy_dup(pkpy_vm*, int)`
Duplicate the value at the given index.
+ `bool pkpy_pop(pkpy_vm*, int)`
Pop `n` values from the stack.
+ `bool pkpy_pop_top(pkpy_vm*)`
Pop the top value from the stack.
+ `bool pkpy_dup_top(pkpy_vm*)`
Duplicate the top value on the stack.
+ `bool pkpy_rot_two(pkpy_vm*)`
Swap the top two values on the stack.
+ `int pkpy_stack_size(pkpy_vm*)`
Get the element count of the stack.
### Basic push, check and convert
+ `pkpy_push_xxx` pushes a value onto the stack.
+ `pkpy_is_xxx` checks if the value at the given index is of the given type.
+ `pkpy_to_xxx` converts the value at the given index to the given type.
Stack index is 0-based instead of 1-based. And it can be negative, which means the index is counted from the top of the stack.
```c
// int
PK_EXPORT bool pkpy_push_int(pkpy_vm*, int);
PK_EXPORT bool pkpy_is_int(pkpy_vm*, int i);
PK_EXPORT bool pkpy_to_int(pkpy_vm*, int i, int* out);
// float
PK_EXPORT bool pkpy_push_float(pkpy_vm*, double);
PK_EXPORT bool pkpy_is_float(pkpy_vm*, int i);
PK_EXPORT bool pkpy_to_float(pkpy_vm*, int i, double* out);
// bool
PK_EXPORT bool pkpy_push_bool(pkpy_vm*, bool);
PK_EXPORT bool pkpy_is_bool(pkpy_vm*, int i);
PK_EXPORT bool pkpy_to_bool(pkpy_vm*, int i, bool* out);
// string
PK_EXPORT bool pkpy_push_string(pkpy_vm*, pkpy_CString);
PK_EXPORT bool pkpy_is_string(pkpy_vm*, int i);
PK_EXPORT bool pkpy_to_string(pkpy_vm*, int i, pkpy_CString* out);
// void_p
PK_EXPORT bool pkpy_push_voidp(pkpy_vm*, void*);
PK_EXPORT bool pkpy_is_voidp(pkpy_vm*, int i);
PK_EXPORT bool pkpy_to_voidp(pkpy_vm*, int i, void** out);
// none
PK_EXPORT bool pkpy_push_none(pkpy_vm*);
PK_EXPORT bool pkpy_is_none(pkpy_vm*, int i);
```
### Special push
+ `pkpy_push_null(pkpy_vm*)`
Push a `PY_NULL` onto the stack. It is used for `pkpy_vectorcall`.
+ `pkpy_push_function(pkpy_vm*, const char* sig, pkpy_CFunction f)`
Push a function onto the stack. `sig` is the function signature, e.g. `add(a: int, b: int) -> int`. `f` is the function pointer.
+ `pkpy_push_module(pkpy_vm*, const char* name)`
Push a new module onto the stack. `name` is the module name. This is not `import`. It creates a new module object.
### Variable access
+ `bool pkpy_getattr(pkpy_vm*, pkpy_CName name)`
Push `b.<name>` onto the stack. Return false if the attribute is not found.
```
[b] -> [b.<name>]
```
+ `bool pkpy_setattr(pkpy_vm*, pkpy_CName name)`
Set `b.<name>` to the value at the top of the stack.
First push the value, then push `b`.
```
[value, b] -> []
```
+ `bool pkpy_getglobal(pkpy_vm*, pkpy_CName name)`
Push a global/builtin variable onto the stack. Return false if the variable is not found.
```
[] -> [value]
```
+ `bool pkpy_setglobal(pkpy_vm*, pkpy_CName name)`
Set a global variable to the value at the top of the stack.
```
[value] -> []
```
+ `bool pkpy_eval(pkpy_vm*, const char* source)`
Evaluate a string and push the result onto the stack.
```
[] -> [result]
```
+ `bool pkpy_unpack_sequence(pkpy_vm*, int size)`
Unpack a sequence at the top of the stack. `size` is the element count of the sequence.
```
[a] -> [a[0], a[1], ..., a[size - 1]]
```
+ `bool pkpy_get_unbound_method(pkpy_vm*, pkpy_CName name)`
It is used for method call.
Get an unbound method from the object at the top of the stack. `name` is the method name.
Also push the object as self.
```
[obj] -> [obj.<name> self]
```
+ `bool pkpy_py_repr(pkpy_vm*)`
Get the repr of the value at the top of the stack.
```
[value] -> [repr(value)]
```
+ `bool pkpy_py_str(pkpy_vm*)`
Get the str of the value at the top of the stack.
```
[value] -> [str(value)]
```
+ `bool pkpy_py_import(pkpy_vm*, const char* name)`
Import a module and push it onto the stack.
```
[] -> [module]
```

View File

@ -6,294 +6,13 @@ order: 18
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
typedef PyVar (*NativeFuncC)(VM*, ArgsView);
```c
typedef bool (*py_CFunction)(int argc, py_Ref argv);
```
+ The first argument is the pointer of `VM` instance.
+ 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.
+ The return value is a `PyVar`, which should not be `nullptr`. If there is no return value, return `vm->None`.
+ `argc` is the number of arguments passed to the function.
+ `argv` is the pointer to the first argument.
## Bind a function or method
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;
}
```
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`.

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(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));
```

View File

@ -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>();
```

View File

@ -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
```

View File

@ -1,2 +1,2 @@
order: 19
order: 0
label: "GSoC"

View File

@ -5,12 +5,11 @@ label: Welcome
# 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.
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

View File

@ -1,8 +0,0 @@
---
icon: package
label: c
---
Interop with pointers and C structs.
https://github.com/pocketpy/pocketpy/blob/main/include/typings/c.pyi

View File

@ -1,8 +0,0 @@
---
icon: package
label: colorsys
---
The same as the standard library module `colorsys` in python 3.11.
https://github.com/python/cpython/blob/3.11/Lib/colorsys.py

View File

@ -1,33 +0,0 @@
---
icon: package
label: csv
---
### `csv.reader(csvfile: list[str]) -> list[list]`
Parse a CSV file into a list of lists.
### `csv.DictReader(csvfile: list[str]) -> list[dict]`
Parse a CSV file into a list of dictionaries.
## Example
```python
import csv
data = """a,b,c
1,2,3
"""
print(csv.reader(data.splitlines()))
# [
# ['a', 'b', 'c'],
# ['1', '2', '3']
# ]
print(csv.DictReader(data.splitlines()))
# [
# {'a': '1', 'b': '2', 'c': '3'}
# ]
```

128
docs/quick-start.md Normal file
View File

@ -0,0 +1,128 @@
---
icon: rocket
order: 20
label: Quick Start
---
You have two options to integrate pkpy into your project.
#### Use the single header file
Download the `pocketpy.h` on our [GitHub Release](https://github.com/pocketpy/pocketpy/releases) page.
And `#include` it in your project. The header can only be included once.
#### Use CMake
Clone the whole repository as a submodule into your project,
In your CMakelists.txt, add the following lines:
```cmake
add_subdirectory(pocketpy)
target_link_libraries(<your_target> pocketpy)
```
See [CMakeLists.txt](https://github.com/pocketpy/pocketpy/blob/main/CMakeLists.txt) for details.
It is safe to use `main` branch in production if CI badge is green.
### Compile flags
To compile it with your project, these flags must be set:
+ `--std=c11` flag must be set
+ For MSVC, `/utf-8` flag must be set
### Get prebuilt binaries
We have prebuilt binaries,
check them out on our [GitHub Actions](https://github.com/pocketpy/pocketpy/actions/workflows/main.yml).
You can download an artifact there which contains the following files.
```
├── android
│   ├── arm64-v8a
│   │   └── libpocketpy.so
│   ├── armeabi-v7a
│   │   └── libpocketpy.so
│   └── x86_64
│   └── libpocketpy.so
├── ios
│   └── libpocketpy.a
├── linux
│   └── x86_64
│   ├── libpocketpy.so
│   └── main
├── macos
│   └── pocketpy.bundle
│   └── Contents
│   ├── Info.plist
│   └── MacOS
│   └── pocketpy
└── windows
└── x86_64
├── main.exe
└── pocketpy.dll
```
### Example
```c
#include "pocketpy.h"
#include <stdio.h>
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() {
// Initialize pocketpy
py_initialize();
// Hello world!
bool ok = py_exec("print('Hello world!')", "<string>", EXEC_MODE, NULL);
if(!ok) goto __ERROR;
// Create a list: [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
py_Ref f_sum = py_getbuiltin(py_name("sum"));
py_push(f_sum);
py_pushnil();
py_push(r0);
ok = py_vectorcall(1, 0);
if(!ok) goto __ERROR;
printf("Sum of the list: %d\n", (int)py_toint(py_retval())); // 6
// Bind native `int_add` as a global variable
py_newnativefunc(r0, int_add);
py_setglobal(py_name("add"), r0);
// Call `add` in python
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;
__ERROR:
py_printexc();
py_finalize();
return 1;
}
```

View File

@ -1,72 +0,0 @@
---
icon: dot
label: 'Access Attributes'
order: 80
---
### Direct access
Some python objects have an instance dict, a.k.a, `__dict__` in cpython.
You can use `obj->attr()` to manipulate the instance dict of an object.
```cpp
VM* vm = new VM();
// get the `builtin` module
PyVar builtins = vm->builtins;
// get `dict` type
PyVar dict = builtins->attr("dict");
// set `pi = 3.14`
builtins->attr().set("pi", py_var(vm, 3.14));
```
However, you cannot call `attr` on an object which does not have an instance dict.
For example, the `int` object.
```cpp
// create a `int` object
PyVar obj = py_var(vm, 1);
// THIS IS WRONG!! WILL LEAD TO A SEGFAULT!!
PyVar add = obj->attr("__add__");
```
To determine whether an object has instance dict or not, you can use this snippet.
```cpp
// 1. call `is_tagged` to check the object supports `->` operator
// 2. call `is_attr_valid` to check the existence of instance dict
PyVar obj = py_var(vm, 1);
bool ok = !is_tagged(obj) && obj->is_attr_valid(); // false
```
### General access
As you can see, direct access does not take care of derived attributes or methods.
In most cases, what you need is `getattr` and `setattr`.
These two methods handle all possible cases.
#### `PyVar getattr(PyVar obj, StrName name, bool throw_err=true)`
This method is equivalent to `getattr` in python.
If the attribute is not found, it will return `nullptr`
or throw an `AttributeError` depending on the value of `throw_err`.
```cpp
// create a `int` object
PyVar obj = py_var(vm, 1);
// get its `__add__` method, which is a `bound_method` object
PyVar add = vm->getattr(obj, "__add__");
// call it (equivalent to `1 + 2`)
PyVar ret = vm->call(add, py_var(vm, 2););
// get the result
int result = py_cast<int>(vm, ret);
std::cout << result << std::endl; // 3
```
#### `void setattr(PyVar, StrName, PyVar)`
This method is equivalent to `setattr` in python.
It raises `TypeError` if the object does not support attribute assignment.

View File

@ -1,71 +0,0 @@
---
icon: dot
label: 'Call Python Function'
order: 70
---
pkpy uses a variant of the [Vectorcall](https://peps.python.org/pep-0590/) protocol (PEP 590).
You can use `call` to invoke any python callable object,
including functions, methods, classes, etc.
For methods, `call_method` can be used.
+ `PyVar call(PyVar obj, ...)`
+ `PyVar call_method(PyVar obj, StrName name, ...)`
### Example
Let's create a `dict` object and set a key-value pair,
which equals to the following python snippet.
```python
obj = {} # declare a `dict`
obj["a"] = 5 # set a key-value pair
print(obj["a"]) # print the value
```
First, create an empty dict object,
```cpp
PyVar tp = vm->builtins->attr("dict");
PyVar obj = vm->call(tp); // this is a `dict`
```
And set a key-value pair,
```cpp
PyVar _0 = py_var(vm, "a");
PyVar _1 = py_var(vm, 5);
vm->call_method(obj, "__setitem__", _0, _1);
```
And get the value,
```cpp
PyVar ret = vm->call_method(obj, "__getitem__", _0);
std::cout << py_cast<int>(vm, i64);
```
If you want to call with dynamic number of arguments,
you should use `vm->vectorcall`. This is a low-level, stack-based API.
1. First push the callable object to the stack.
2. Push the `self` object to the stack. If there is no `self`, push `PY_NULL`.
3. Push the arguments to the stack.
4. Call `vm->vectorcall` with the number of arguments.
```cpp
PyVar f_sum = vm->builtins->attr("sum");
List args(N); // a list of N arguments
vm->s_data.push_back(f_sum);
vm->s_data.push_back(PY_NULL); // self
for(PyVar arg : args) {
vm->s_data.push_back(arg);
}
PyVar ret = vm->vectorcall(args.size());
```

View File

@ -1,30 +0,0 @@
---
icon: dot
label: 'Advanced Config'
order: -2
---
### Enable os-related features
If you want to enable os-related features, you can do this before including `pocketpy.h`.
```cpp
#define PK_ENABLE_OS 1
#include <pocketpy.h>
```
### Working with multiple threads
pkpy does not support multi-threading. But you can create multiple `VM` instances and run them in different threads.
You can do the following to ensure thread safety for `VM` instances:
```cpp
#define PK_ENABLE_THREAD 1
#include <pocketpy.h>
```
### Full config
You can take a look at `include/pocketpy/config.h` to see all the available configuration macros.

View File

@ -1,59 +0,0 @@
---
icon: dot
label: 'Execute Python Code'
order: 93
---
### Simple execution
Once you have a `VM` instance, you can execute python code by calling `exec` method.
#### `PyVar exec(Str source, Str filename, CompileMode mode, PyVar _module=nullptr)`
+ `source`, the python source code to be executed
+ `filename`, the filename of the source code. This is used for error reporting
+ `mode`, the compile mode. See below for details
+ `module`, the module where the code will be executed. If `nullptr`, the code will be executed in the `__main__` module
`exec` handles possible exceptions and returns a `PyVar`.
If the execution is not successful, e.g. a syntax error or a runtime exception,
the return value will be `nullptr`.
There are also overloaded versions of `exec` and `eval`, which is useful for simple execution:
+ `PyVar exec(Str source)`
+ `PyVar eval(Str source)`
### Compile mode
The `mode` parameter controls how the source code is compiled. There are 5 possible values:
+ `EXEC_MODE`, this is the default mode. Just do normal execution.
+ `EVAL_MODE`, this mode is used for evaluating a single expression. The `source` should be a single expression. It cannot contain any statements.
+ `REPL_MODE`, this mode is used for REPL. It is similar to `EXEC_MODE`, but generates `PRINT_EXPR` opcode when necessary.
+ `CELL_MODE`, this mode is designed for Jupyter like execution. It is similar to `EXEC_MODE`, but generates `PRINT_EXPR` opcode when necessary.
+ `JSON_MODE`, this mode is used for JSON parsing. It is similar to `EVAL_MODE`, but uses a lexing rule designed for JSON.
### Fine-grained execution
In some cases, you may want to execute python code in a more fine-grained way.
These two methods are provided for this purpose:
+ `CodeObject_ compile(Str source, Str filename, CompileMode mode, bool unknown_global_scope)`
+ `PyVar _exec(CodeObject_ co, PyVar _module)`
1. `compile` compiles the source code into a `CodeObject_` instance. Leave `unknown_global_scope` to `false` if you don't know what it means.
2. `_exec` executes the `CodeObject_` instance.
!!!
`_exec` does not handle exceptions, you need to use `try..catch` manually.
!!!
```cpp
try{
CodeObject_ code = vm->compile("a[0]", "main.py", EXEC_MODE, false);
vm->_exec(code, vm->_main);
}catch(TopLevelException e){
// use e.summary() to get a summary of the exception
std::cerr << e.summary() << std::endl;
}
```

View File

@ -1,3 +0,0 @@
icon: rocket
order: 20
label: Quick Start

View File

@ -1,167 +0,0 @@
---
icon: dot
label: 'Installation'
order: 100
---
You have two options to integrate pkpy into your project.
#### Use the single header file
Download the `pocketpy.h` on our [GitHub Release](https://github.com/pocketpy/pocketpy/releases) page.
And `#include` it in your project. The header can only be included once.
#### Use CMake
Clone the whole repository as a submodule into your project,
In your CMakelists.txt, add the following lines:
```cmake
add_subdirectory(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.
It is safe to use `main` branch in production if CI badge is green.
### Compile flags
To compile it with your project, these flags must be set:
+ `--std=c++17` flag must be set
+ RTTI must be enabled
+ Exception must be enabled
+ 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
We have prebuilt binaries,
check them out on our [GitHub Actions](https://github.com/pocketpy/pocketpy/actions/workflows/main.yml).
You can download an artifact there which contains the following files.
```
├── android
│   ├── arm64-v8a
│   │   └── libpocketpy.so
│   ├── armeabi-v7a
│   │   └── libpocketpy.so
│   └── x86_64
│   └── libpocketpy.so
├── ios
│   └── libpocketpy.a
├── linux
│   └── x86_64
│   ├── libpocketpy.so
│   └── main
├── macos
│   └── pocketpy.bundle
│   └── Contents
│   ├── Info.plist
│   └── MacOS
│   └── pocketpy
└── windows
└── x86_64
├── main.exe
└── pocketpy.dll
```
### Example
```cpp
#include "pocketpy.h"
using namespace pkpy;
int main(){
// Create a virtual machine
VM* vm = new VM();
// Hello world!
vm->exec("print('Hello world!')");
// Create a list
vm->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
// 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);
});
// 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
// Dispose the virtual machine
delete vm;
return 0;
```
### Overview
pkpy's C++ interfaces are organized in an object-oriented way.
All classes are located in `pkpy` namespace.
The most important class is the `VM` class. A `VM` instance is a python virtual machine which holds all necessary runtime states, including callstack, modules, variables, etc.
A process can have multiple `VM` instances. Each `VM` instance is independent from each other.
!!!
Always use C++ `new` operator to create a `VM` instance.
DO NOT declare it on the stack. It may cause stack overflow.
!!!
```cpp
VM* vm = new VM();
```
The constructor can take 1 extra parameters.
#### `VM(bool enable_os=true)`
+ `enable_os`, whether to enable OS-related features or not. This setting controls the availability of privileged modules such os `io` and `os` as well as builtin function `open`. **It is designed for sandboxing.**
When you are done with the `VM` instance, use `delete` operator to dispose it.
```cpp
delete vm;
```
### Hook standard buffer
By default, pkpy outputs all messages and errors to `stdout` and `stderr`.
You can redirect them to your own buffer by setting `vm->_stdout` and `vm->_stderr`.
These two fields are C function pointers with the following signature:
```cpp
void(*)(const char*, int);
```
Or you can override these two virtual functions:
```cpp
virtual void stdout_write(const Str& s){
_stdout(s.data, s.size);
}
virtual void stderr_write(const Str& s){
_stderr(s.data, s.size);
}
```

View File

@ -1,117 +0,0 @@
---
icon: dot
label: 'Interop with PyVar'
order: 90
---
In pkpy, any python object is represented by a `PyVar`.
### Create `PyVar` from C type
A set of overloaded function `PyVar py_var(VM* vm, ...)` were implemented to
create a `PyVar` from a supported C type.
Assume we have a `VM* vm` instance.
You can create a python `int` object from a C `i64` type:
```cpp
i64 i = 2;
PyVar obj = py_var(vm, i);
```
Each python type has a corresponding C type, for example, `int` in python is `i64` in C.
python's `list` corresponds to `List`, `str` corresponds to `Str`, etc.
For strings, we have defined
a set of overloaded version including `const char*`, `std::string`, `std::string_view`, `Str`, etc.
```cpp
PyVar obj = py_var(vm, "abc"); // create a python str object
```
A more complex example is to create a python `list`.
In the following code, we create a `list` equals to `[0, 1, 2, 3]`.
```cpp
List list;
for (i64 i = 0; i < 4; i++) {
list.push_back(py_var(vm, i));
}
obj = py_var(vm, std::move(list)); // create a python list object
```
Please note that `std::move` is used here to avoid unnecessary copy.
Most types have both a rvalue and a lvalue version of `py_var` function.
### Access internal C type of `PyVar`
A set of template function `T py_cast<T>(VM* vm, PyVar obj)` were implemented.
```cpp
i64 i = 2;
PyVar obj = py_var(vm, i);
// cast a PyVar to C i64
i64 j = py_cast<i64>(vm, obj);
```
The `py_cast` function will check the type of `obj` before casting.
If the type is not matched, a `TypeError` will be thrown.
However, this type check has a cost. If you are sure about the type of `obj`,
you can use the underscore version `_py_cast` to skip the type check.
```cpp
// cast a PyVar to C i64 (unsafe but faster)
i64 j = _py_cast<i64>(vm, obj);
```
For complex objects like `list`, we can use reference cast to avoid unnecessary copy.
```cpp
PyVar obj = py_var(vm, List());
// reference cast (no copy)
List& list = py_cast<List&>(vm, obj);
```
### Check type of `PyVar`
Each `PyVar` has a `Type type` field to indicate its type.
`Type` is just an integer which is the global index in `vm->_all_types`.
`VM` class has a set of predefined `Type` constants for quick access.
They are prefixed by `tp_`. For example, `tp_object`(object),
`tp_int`(int), `tp_str`(str), `tp_list`(list), etc.
Types are divided into **tagged type** and **non-tagged type**.
+ small `int` objects are tagged.
+ Other types are non-tagged type.
To determine whether a `PyVar` is of a specific type,
you can use the following functions:
+ `bool is_type(PyVar obj, Type type)`
+ `bool is_int(PyVar obj)`
+ `bool is_float(PyVar obj)`
+ `bool is_tagged(PyVar obj)`
```cpp
PyVar obj = py_var(vm, 1);
bool ok = is_type(obj, vm->tp_int); // true
ok = is_int(obj); // true
ok = is_tagged(obj); // true
ok = is_type(obj, vm->tp_float); // false
ok = is_float(obj); // false
```
Simply put, `is_type` is the most general function and can check any types.
Other variants are designed for specific types and are faster.
You can also use `check_` prefix functions assert the type of a `PyVar`,
which will throw `TypeError` on failure.
+ `void check_type(PyVar obj, Type type)`

View File

@ -1,41 +0,0 @@
---
icon: dot
label: 'Miscellaneous'
order: 0
---
## The scope lock of gc
Sometimes you need to use the following code to prevent the gc from collecting objects.
```cpp
auto _lock = vm->gc_scope_lock();
```
The scope lock is required if you create a PyVar and then try to run python-level bytecodes.
For example, you create a temporary object on the stack and then call `vm->py_next`.
```cpp
void some_func(VM* vm){
PyVar obj = VAR(List(5));
// unsafe
PyVar iter = vm->py_iter(obj);
PyVar next = vm->py_next(iter);
}
```
Because users can have an overload of `__next__`, this call is unsafe.
When the vm is running python-level bytecodes, gc may start and delete your temporary object.
The scope lock prevents this from happening.
```cpp
void some_func(VM* vm){
PyVar obj = VAR(List(5));
// safe
auto _lock = vm->gc_scope_lock();
PyVar iter = vm->py_iter(obj);
PyVar next = vm->py_next(iter);
}
```

View File

@ -1,67 +0,0 @@
---
icon: dot
label: 'Create Modules'
order: 50
---
Modules are stored in `vm->_modules` and `vm->_lazy_modules`.
They are both dict-like objects.
### Lazy modules
A lazy module is a python source file.
It is compiled and executed when it is imported.
Use `[]` operator to add a lazy module.
```cpp
vm->_lazy_modules["test"] = "pi = 3.14";
```
```python
import test
print(test.pi) # 3.14
```
### Native modules
A native module is a module written in c++ or mixed c++/python.
Native modules are always compiled and executed when the VM is created.
To creata a native module, use `vm->new_module(Str name)`.
```cpp
PyObject* mod = vm->new_module("test");
mod->attr().set("pi", py_var(vm, 3.14));
vm->bind(mod, "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);
});
```
```python
import test
print(test.pi) # 3.14
print(test.add(1, 2)) # 3
```
### Module resolution order
When you do `import` a module, the VM will try to find it in the following order:
1. Search `vm->_modules`, if found, return it.
2. Search `vm->_lazy_modules`, if found, compile and execute it, then return it.
3. Try `vm->_import_handler`.
### Customized import handler
You can use `vm->_import_handler` to provide a custom import handler for the 3rd step.
### Import module via cpp
You can use `vm->py_import` to import a module.
This is equivalent to `import` in python.
Return the module object if success.

View File

@ -16,10 +16,6 @@ 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: "Github"
icon: mark-github
link: https://github.com/blueloveth/pocketpy

View File

@ -1,4 +0,0 @@
#pragma once
#include "pocketpy/interpreter/vm.hpp"
// dummy header for ceval.cpp

View File

@ -13,7 +13,7 @@ def get_loc_for_dir(path):
loc_ex = 0
for root, dirs, files in os.walk(path):
for file in files:
if file.endswith('.h') or file.endswith('.cpp') or file.endswith('.hpp'):
if file.endswith('.h') or file.endswith('.c') or file.endswith('.h'):
_i = get_loc(os.path.join(root, file))
if file.startswith('_'):
loc_ex += _i

View File

@ -1,4 +1,3 @@
<!--
Note:
@ -30,6 +29,7 @@ DEALINGS IN THE SOFTWARE.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -41,136 +41,201 @@ DEALINGS IN THE SOFTWARE.
<link rel="stylesheet" href="static/highlight.js/github-dark-dimmed.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
@import url("https://fonts.googleapis.com/css2?family=Lato:wght@300&family=PT+Mono&display=swap");
<style>
@import url("https://fonts.googleapis.com/css2?family=Lato:wght@300&family=PT+Mono&display=swap");
.hljs{display:block;overflow-x:auto;padding:.5em;background:#282a36}
.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-section,.hljs-link{color:#73cbde}
.hljs,.hljs-subst{color:#f8f8f2}
.hljs-string,.hljs-title,.hljs-name,.hljs-type,.hljs-attribute,.hljs-symbol,
.hljs-bullet,.hljs-addition,.hljs-variable,.hljs-template-tag,.hljs-template-variable{color:#f1fa8c}
.hljs-comment,.hljs-quote,.hljs-deletion,.hljs-meta{color:#6272a4}
.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-title,.hljs-section,.hljs-doctag,
.hljs-type,.hljs-name,.hljs-strong{font-weight:bold}.hljs-emphasis{font-style:italic}
.hljs {
display: block;
overflow-x: auto;
padding: .5em;
background: #282a36
}
:root {
--window-width: 80%;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-section,
.hljs-link {
color: #73cbde
}
body {
background-color: #778abb;
font-family: Lato, sans-serif;
font-weight: 300;
font-size: 15px;
margin: 0;
}
*,
*:before,
*:after {
box-sizing: border-box;
}
*:focus {
outline: none;
}
a,
a:visited,
a:active {
color: black;
}
main {
min-height: 100vh;
display: flex;
align-items: center;
flex-direction: column;
}
.title {
color: #fff;
text-align: center;
font-weight: 300;
font-size: 34px;
margin-top: 20px;
}
.window {
width: var(--window-width);
border-radius: 6px;
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin-bottom: 20px;
}
.window .window-header {
height: 25px;
background: Gainsboro;
position: relative;
}
.window .window-header .action-buttons {
position: absolute;
top: 50%;
left: 10px;
margin-top: -5px;
width: 10px;
height: 10px;
background: Crimson;
border-radius: 50%;
box-shadow: 15px 0 0 Orange, 30px 0 0 LimeGreen;
}
#code-editor {
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
0 3px 1px -2px rgba(0, 0, 0, 0.2);
font-family: "PT Mono", monospace;
font-size: 14px;
font-weight: 400;
min-height: 300px;
letter-spacing: normal;
line-height: 20px;
padding: 10px;
resize: none !important;
tab-size: 4;
}
#run-button {
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
cursor: pointer;
}
#code-editor.hljs {
padding: 10px;
}
#output-wrapper {
font-family: "PT Mono", monospace;
width: var(--window-width);
min-height: 50px;
background-color: #282a36;
border-radius: 6px;
padding: 10px;
color: white;
margin:0 !important;
}
#code-output span.error-line {
color: #ec5424;
}
.controls {
font-size: 14px;
position: absolute;
top: 50%;
right: 10px;
margin-top: -10px;
display: flex;
}
.controls > div:first-child > a {
display: inline-block;
width: 40px;
}
.features {
width: 547px;
font-size: 16px;
margin-bottom: 30px;
}
</style>
.hljs,
.hljs-subst {
color: #f8f8f2
}
.hljs-string,
.hljs-title,
.hljs-name,
.hljs-type,
.hljs-attribute,
.hljs-symbol,
.hljs-bullet,
.hljs-addition,
.hljs-variable,
.hljs-template-tag,
.hljs-template-variable {
color: #f1fa8c
}
.hljs-comment,
.hljs-quote,
.hljs-deletion,
.hljs-meta {
color: #6272a4
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-title,
.hljs-section,
.hljs-doctag,
.hljs-type,
.hljs-name,
.hljs-strong {
font-weight: bold
}
.hljs-emphasis {
font-style: italic
}
:root {
--window-width: 80%;
}
body {
background-color: #778abb;
font-family: Lato, sans-serif;
font-weight: 300;
font-size: 15px;
margin: 0;
}
*,
*:before,
*:after {
box-sizing: border-box;
}
*:focus {
outline: none;
}
a,
a:visited,
a:active {
color: black;
}
main {
min-height: 100vh;
display: flex;
align-items: center;
flex-direction: column;
}
.title {
color: #fff;
text-align: center;
font-weight: 300;
font-size: 34px;
margin-top: 20px;
}
.window {
width: var(--window-width);
border-radius: 6px;
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin-bottom: 20px;
}
.window .window-header {
height: 25px;
background: Gainsboro;
position: relative;
}
.window .window-header .action-buttons {
position: absolute;
top: 50%;
left: 10px;
margin-top: -5px;
width: 10px;
height: 10px;
background: Crimson;
border-radius: 50%;
box-shadow: 15px 0 0 Orange, 30px 0 0 LimeGreen;
}
#code-editor {
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
0 3px 1px -2px rgba(0, 0, 0, 0.2);
font-family: "PT Mono", monospace;
font-size: 14px;
font-weight: 400;
min-height: 300px;
letter-spacing: normal;
line-height: 20px;
padding: 10px;
resize: none !important;
tab-size: 4;
}
#run-button {
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
cursor: pointer;
}
#code-editor.hljs {
padding: 10px;
}
#output-wrapper {
font-family: "PT Mono", monospace;
width: var(--window-width);
min-height: 50px;
background-color: #282a36;
border-radius: 6px;
padding: 10px;
color: white;
margin: 0 !important;
}
#code-output span.error-line {
color: #ec5424;
}
.controls {
font-size: 14px;
position: absolute;
top: 50%;
right: 10px;
margin-top: -10px;
display: flex;
}
.controls>div:first-child>a {
display: inline-block;
width: 40px;
}
.features {
width: 547px;
font-size: 16px;
margin-bottom: 30px;
}
</style>
<title>Try Online</title>
</head>
<body id="tryonline-body">
<main>
@ -183,74 +248,61 @@ margin-bottom: 30px;
</div>
</div>
<div class="window-body">
<div id="code-editor" class="language-python" data-gramm="false"># A recursive fibonacci function.
def fib(n):
if n &lt; 2:
return n
return fib(n-1) + fib(n-2)
# Prints all fibonacci from 0 to 10 exclusive.
for i in range(10):
print(f"fib({i}) = {fib(i)}")
</div>
<div id="code-editor" class="language-python" data-gramm="false"></div>
</div>
</div>
<pre id="output-wrapper"><div id="code-output">...</div></pre>
<br>
</main>
<script>
var Module = {
onRuntimeInitialized: function () {
Module.ccall('py_initialize', null, [], []);
},
print: function (text) {
console.log(text);
code_output.innerText += text + '\n';
},
onabort: function (what) {
code_output.innerText += 'Aborted: ' + what + '\n';
Module.ccall('py_finalize', null, [], []);
}
};
</script>
<script src="lib/pocketpy.js"></script>
<script>
let code_editor = document.querySelector('#code-editor');
let code_output = document.querySelector('#code-output');
let run_button = document.querySelector('#run-button');
<script>
code_editor.textContent = '# A recursive fibonacci function.\ndef fib(n):\n if n < 2:\n return n\n return fib(n-1) + fib(n-2)\n\n# Prints all fibonacci from 0 to 10 exclusive.\nfor i in range(10):\n print(f"fib({i}) = {fib(i)}")\n';
let code_editor = document.querySelector('#code-editor');
let code_output = document.querySelector('#code-output');
let run_button = document.querySelector('#run-button');
let highlight = withLineNumbers(function (editor) {
editor.textContent = editor.textContent;
hljs.highlightElement(editor);
});
let highlight = withLineNumbers(function(editor) {
editor.textContent = editor.textContent;
hljs.highlightElement(editor);
});
highlight(code_editor);
CodeJar(code_editor, highlight); //, { indentOn: /[(\[{:]$/});
highlight(code_editor);
CodeJar(code_editor, highlight); //, { indentOn: /[(\[{:]$/});
var Module = {
onRuntimeInitialized: function() {
Module.ccall('py_initialize', null, [], []);
},
print: function(text) {
console.log(text);
code_output.innerText += text + '\n';
},
onabort: function(what) {
code_output.innerText += 'Aborted: ' + what + '\n';
Module.ccall('py_finalize', null, [], []);
}
};
window.onload = function() {
var script = document.createElement('script');
script.src = 'lib/pocketpy.js';
document.head.appendChild(script);
run_button.onclick = function() {
code_output.innerText = '';
const source = code_editor.textContent;
var ok = Module.ccall(
'py_exec', 'boolean', ['string', 'string', 'number', 'number'],
[source, 'main.py', 0, 0]
);
if (!ok) {
Module.ccall('py_printexc', null, [], []);
Module.ccall('py_clearexc', null, ['number'], [0]);
window.onload = function () {
run_button.onclick = function () {
code_output.innerText = '';
const source = code_editor.textContent;
var ok = Module.ccall(
'py_exec', 'boolean', ['string', 'string', 'number', 'number'],
[source, 'main.py', 0, 0]
);
if (!ok) {
Module.ccall('py_printexc', null, [], []);
Module.ccall('py_clearexc', null, ['number'], [0]);
}
}
}
}
}
</script>
</script>
</body>
</html>
</html>