mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-28 07:20:17 +00:00
Compare commits
7 Commits
76c251fb7f
...
145b36b677
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
145b36b677 | ||
|
|
9d0d4d1841 | ||
|
|
26c76960e9 | ||
|
|
f22dda3125 | ||
|
|
2de9efc0e9 | ||
|
|
96a760dfd8 | ||
|
|
d3fd75d0be |
@ -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
9
docs/2_0.md
Normal 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.
|
||||
222
docs/2_0_0.md
222
docs/2_0_0.md
@ -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`.
|
||||
@ -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
|
||||
icon: code
|
||||
order: 1
|
||||
order: 15
|
||||
@ -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
|
||||
@ -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`.
|
||||
@ -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]
|
||||
```
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
@ -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"
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
128
docs/quick-start.md
Normal 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;
|
||||
}
|
||||
```
|
||||
@ -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.
|
||||
@ -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());
|
||||
```
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
```
|
||||
@ -1,3 +0,0 @@
|
||||
icon: rocket
|
||||
order: 20
|
||||
label: Quick Start
|
||||
@ -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);
|
||||
}
|
||||
```
|
||||
@ -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)`
|
||||
|
||||
@ -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);
|
||||
}
|
||||
```
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "pocketpy/interpreter/vm.hpp"
|
||||
// dummy header for ceval.cpp
|
||||
@ -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
|
||||
|
||||
416
web/index.html
416
web/index.html
@ -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 < 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>
|
||||
Loading…
x
Reference in New Issue
Block a user