mirror of
https://github.com/pocketpy/pocketpy
synced 2025-11-09 21:20:17 +00:00
Merge branch 'main' of https://github.com/blueloveTH/pocketpy into build
This commit is contained in:
commit
9c6119d2f1
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
@ -1,5 +1,15 @@
|
||||
name: build
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'web/**'
|
||||
- '**.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'web/**'
|
||||
- '**.md'
|
||||
jobs:
|
||||
build_win:
|
||||
runs-on: windows-latest
|
||||
|
||||
@ -21,19 +21,12 @@ if(NOT ${PREBUILD_RESULT} EQUAL 0)
|
||||
message(FATAL_ERROR "prebuild.py: ${PREBUILD_RESULT}")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_compile_options("/utf-8")
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -O3")
|
||||
elseif(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /utf-8")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "/O2")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /utf-8 /O2")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -O2")
|
||||
endif()
|
||||
|
||||
include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
|
||||
62
README.md
62
README.md
@ -38,7 +38,7 @@ These platforms are officially tested.
|
||||
+ Android 64-bit / 32-bit
|
||||
+ iOS 64-bit
|
||||
+ Emscripten 32-bit
|
||||
+ Raspberry Pi 64-bit
|
||||
+ Raspberry Pi OS 64-bit
|
||||
|
||||
## Quick Start
|
||||
|
||||
@ -56,11 +56,12 @@ To compile it with your project, these flags must be set:
|
||||
|
||||
+ `--std=c++17` flag must be set
|
||||
+ Exception must be enabled
|
||||
+ For MSVC, `/utf-8` flag must be set
|
||||
|
||||
For development build on Linux, use this snippet.
|
||||
```bash
|
||||
# prerequisites
|
||||
sudo apt-get install libc++-dev libc++abi-dev clang++
|
||||
sudo apt-get install libc++-dev libc++abi-dev clang
|
||||
# build the repo
|
||||
bash build.sh
|
||||
# unittest
|
||||
@ -114,26 +115,37 @@ for a quick overview of the supported features.
|
||||
|
||||
| Name | Example | Supported |
|
||||
| --------------- | ------------------------------- | --------- |
|
||||
| If Else | `if..else..elif` | YES |
|
||||
| Loop | `for/while/break/continue` | YES |
|
||||
| Function | `def f(x,*args,y=1):` | YES |
|
||||
| Subclass | `class A(B):` | YES |
|
||||
| List | `[1, 2, 'a']` | YES |
|
||||
| ListComp | `[i for i in range(5)]` | YES |
|
||||
| Slice | `a[1:2], a[:2], a[1:]` | YES |
|
||||
| Tuple | `(1, 2, 'a')` | YES |
|
||||
| Dict | `{'a': 1, 'b': 2}` | YES |
|
||||
| F-String | `f'value is {x}'` | YES |
|
||||
| Unpacking | `a, b = 1, 2` | YES |
|
||||
| Star Unpacking | `a, *b = [1, 2, 3]` | YES |
|
||||
| Exception | `raise/try..catch` | YES |
|
||||
| Dynamic Code | `eval()/exec()` | YES |
|
||||
| Reflection | `hasattr()/getattr()/setattr()` | YES |
|
||||
| Import | `import/from..import` | YES |
|
||||
| Context Block | `with <expr> as <id>:` | YES |
|
||||
| Type Annotation | `def f(a:int, b:float=1)` | YES |
|
||||
| Generator | `yield i` | YES |
|
||||
| Decorator | `@cache` | YES |
|
||||
| If Else | `if..else..elif` | ✅ |
|
||||
| Loop | `for/while/break/continue` | ✅ |
|
||||
| Function | `def f(x,*args,y=1):` | ✅ |
|
||||
| Subclass | `class A(B):` | ✅ |
|
||||
| List | `[1, 2, 'a']` | ✅ |
|
||||
| ListComp | `[i for i in range(5)]` | ✅ |
|
||||
| Slice | `a[1:2], a[:2], a[1:]` | ✅ |
|
||||
| Tuple | `(1, 2, 'a')` | ✅ |
|
||||
| Dict | `{'a': 1, 'b': 2}` | ✅ |
|
||||
| F-String | `f'value is {x}'` | ✅ |
|
||||
| Unpacking | `a, b = 1, 2` | ✅ |
|
||||
| Star Unpacking | `a, *b = [1, 2, 3]` | ✅ |
|
||||
| Exception | `raise/try..catch` | ✅ |
|
||||
| Dynamic Code | `eval()/exec()` | ✅ |
|
||||
| Reflection | `hasattr()/getattr()/setattr()` | ✅ |
|
||||
| Import | `import/from..import` | ✅ |
|
||||
| Context Block | `with <expr> as <id>:` | ✅ |
|
||||
| Type Annotation | `def f(a:int, b:float=1)` | ✅ |
|
||||
| Generator | `yield i` | ✅ |
|
||||
| Decorator | `@cache` | ✅ |
|
||||
|
||||
## Used By
|
||||
|
||||
| | Description |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| [TIC-80](https://github.com/nesbox/TIC-80) | TIC-80 is a fantasy computer for making, playing and sharing tiny games. |
|
||||
| [ct-py](https://github.com/blueloveTH/ct-py) | Ct.py🥕 is cross-platform 2D game framework built on raylib and imgui. |
|
||||
| [MiniPythonIDE](https://github.com/CU-Production/MiniPythonIDE) | A python ide base on pocketpy |
|
||||
| [py-js](https://github.com/shakfu/py-js) | Python3 externals for Max / MSP |
|
||||
|
||||
Submit a pull request to add your project here.
|
||||
|
||||
## Contribution
|
||||
|
||||
@ -146,7 +158,11 @@ All kinds of contributions are welcome.
|
||||
- any suggestions
|
||||
- any questions
|
||||
|
||||
Check our [Coding Style Guide](https://pocketpy.dev/coding_style_guide/) if you want to contribute C++ code.
|
||||
If you find pocketpy useful, consider star this repository (●'◡'●)
|
||||
|
||||
## Sponsor me
|
||||
|
||||
You can sponsor me via [Github Sponsors](https://github.com/sponsors/blueloveTH). Your sponsorship will help me develop this project continuously.
|
||||
|
||||
## Reference
|
||||
|
||||
|
||||
40
README_zh.md
40
README_zh.md
@ -27,7 +27,7 @@ pkpy 支持任何拥有 C++17 编译器的平台。
|
||||
+ Android 64-bit / 32-bit
|
||||
+ iOS 64-bit
|
||||
+ Emscripten 32-bit
|
||||
+ Raspberry Pi 64-bit
|
||||
+ Raspberry Pi OS 64-bit
|
||||
|
||||
## 快速上手
|
||||
|
||||
@ -76,25 +76,25 @@ int main(){
|
||||
|
||||
| 特性 | 示例 | 支持 |
|
||||
| ------------ | ------------------------------- | ---- |
|
||||
| 分支 | `if..else..elif` | YES |
|
||||
| 循环 | `for/while/break/continue` | YES |
|
||||
| 函数 | `def f(x,*args,y=1):` | YES |
|
||||
| 类与继承 | `class A(B):` | YES |
|
||||
| 列表 | `[1, 2, 'a']` | YES |
|
||||
| 列表生成式 | `[i for i in range(5)]` | YES |
|
||||
| 切片 | `a[1:2], a[:2], a[1:]` | YES |
|
||||
| 元组 | `(1, 2, 'a')` | YES |
|
||||
| 字典 | `{'a': 1, 'b': 2}` | YES |
|
||||
| 格式化字符串 | `f'value is {x}'` | YES |
|
||||
| 序列解包 | `a, b = 1, 2` | YES |
|
||||
| 异常 | `raise/try..catch` | YES |
|
||||
| 动态分发 | `eval()/exec()` | YES |
|
||||
| 反射 | `hasattr()/getattr()/setattr()` | YES |
|
||||
| 导入模块 | `import/from..import` | YES |
|
||||
| 上下文管理器 | `with <expr> as <id>:` | YES |
|
||||
| 类型标注 | `def f(a:int, b:float=1)` | YES |
|
||||
| 生成器 | `yield i` | YES |
|
||||
| 装饰器 | `@cache` | YES |
|
||||
| 分支 | `if..else..elif` | ✅ |
|
||||
| 循环 | `for/while/break/continue` | ✅ |
|
||||
| 函数 | `def f(x,*args,y=1):` | ✅ |
|
||||
| 类与继承 | `class A(B):` | ✅ |
|
||||
| 列表 | `[1, 2, 'a']` | ✅ |
|
||||
| 列表生成式 | `[i for i in range(5)]` | ✅ |
|
||||
| 切片 | `a[1:2], a[:2], a[1:]` | ✅ |
|
||||
| 元组 | `(1, 2, 'a')` | ✅ |
|
||||
| 字典 | `{'a': 1, 'b': 2}` | ✅ |
|
||||
| 格式化字符串 | `f'value is {x}'` | ✅ |
|
||||
| 序列解包 | `a, b = 1, 2` | ✅ |
|
||||
| 异常 | `raise/try..catch` | ✅ |
|
||||
| 动态分发 | `eval()/exec()` | ✅ |
|
||||
| 反射 | `hasattr()/getattr()/setattr()` | ✅ |
|
||||
| 导入模块 | `import/from..import` | ✅ |
|
||||
| 上下文管理器 | `with <expr> as <id>:` | ✅ |
|
||||
| 类型标注 | `def f(a:int, b:float=1)` | ✅ |
|
||||
| 生成器 | `yield i` | ✅ |
|
||||
| 装饰器 | `@cache` | ✅ |
|
||||
|
||||
## 参考
|
||||
|
||||
|
||||
11
build.ps1
11
build.ps1
@ -1,9 +1,12 @@
|
||||
if (Test-Path build) {
|
||||
Remove-Item -Recurse -Force build
|
||||
}
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
New-Item -ItemType Directory -Path build
|
||||
Push-Location build
|
||||
|
||||
cmake ..
|
||||
cmake --build . --config Release
|
||||
cp Release/main.exe ../
|
||||
cp Release/pocketpy.dll ../
|
||||
|
||||
Copy-Item "Release\main.exe" -Destination ".." # Note: NTFS uses backslash (\) instead of slashes (*nix, /)
|
||||
Copy-Item "Release\pocketpy.dll" -Destination ".."
|
||||
|
||||
12
build.sh
12
build.sh
@ -10,24 +10,22 @@ fi
|
||||
# Check if clang++ is installed
|
||||
if ! type -P clang++ >/dev/null 2>&1; then
|
||||
echo "clang++ is required and not installed. Kindly install it."
|
||||
echo "Run: sudo apt-get install libc++-dev libc++abi-dev clang++"
|
||||
echo "Run: sudo apt-get install libc++-dev libc++abi-dev clang"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Requirements satisfied: python3 and clang++ are installed."
|
||||
echo "Requirements satisfied: python3 and clang are installed."
|
||||
echo "It takes a moment to finish building."
|
||||
echo ""
|
||||
echo "> Running prebuild.py... "
|
||||
|
||||
python3 prebuild.py
|
||||
|
||||
# echo -n "Finding source files... "
|
||||
SRC=$(find src/ -name "*.cpp")
|
||||
# echo "Done"
|
||||
|
||||
echo "> Compiling and linking source files... "
|
||||
|
||||
FLAGS="-std=c++17 -O2 -stdlib=libc++ -Wfatal-errors -Iinclude"
|
||||
FLAGS="-std=c++17 -O1 -stdlib=libc++ -Wfatal-errors -Iinclude"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
LIB_EXTENSION=".dylib"
|
||||
FLAGS="$FLAGS -undefined dynamic_lookup"
|
||||
@ -42,10 +40,10 @@ clang++ $FLAGS -o libpocketpy$LIB_EXTENSION $SRC -fPIC -shared -ldl
|
||||
# compile main.cpp and link to libpocketpy.so
|
||||
echo "> Compiling main.cpp and linking to libpocketpy$LIB_EXTENSION..."
|
||||
|
||||
clang++ $FLAGS -o main src2/main.cpp -L. -lpocketpy $LINK_FLAGS
|
||||
clang++ $FLAGS -o main -O1 src2/main.cpp -L. -lpocketpy $LINK_FLAGS
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Build completed successfully. To use pocketpy, run : ./main"
|
||||
echo "Build completed. Type \"./main\" to enter REPL."
|
||||
else
|
||||
echo "Build failed."
|
||||
exit 1
|
||||
|
||||
104
docs/bindings.md
104
docs/bindings.md
@ -49,6 +49,65 @@ vm->bind(obj,
|
||||
});
|
||||
```
|
||||
|
||||
#### 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 `PyObject*` 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 a POD type smaller than 8 bytes.
|
||||
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
|
||||
```
|
||||
|
||||
The 3rd way is to change the macro `PK_ENABLE_STD_FUNCTION` in `config.h`:
|
||||
```cpp
|
||||
#define PK_ENABLE_STD_FUNCTION 0 // => 1
|
||||
```
|
||||
|
||||
Then you can use standard capture list in lambda.
|
||||
|
||||
### Bind a struct
|
||||
|
||||
Assume you have a struct `Point` declared as follows.
|
||||
@ -132,6 +191,47 @@ int main(){
|
||||
}
|
||||
```
|
||||
|
||||
#### Handle gc for container types
|
||||
|
||||
If your custom type stores `PyObject*` in its fields, you need to handle gc for them.
|
||||
|
||||
```cpp
|
||||
struct Container{
|
||||
PY_CLASS(Container, builtins, Container)
|
||||
|
||||
PyObject* a;
|
||||
std::vector<PyObject*> b;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Add a magic method `_gc_mark() const` to your custom type.
|
||||
|
||||
```cpp
|
||||
struct Container{
|
||||
PY_CLASS(Container, builtins, Container)
|
||||
|
||||
PyObject* a;
|
||||
std::vector<PyObject*> b;
|
||||
// ...
|
||||
|
||||
void _gc_mark() const{
|
||||
// mark a
|
||||
if(a) PK_OBJ_MARK(a);
|
||||
|
||||
// mark elements in b
|
||||
for(PyObject* obj : b){
|
||||
if(obj) PK_OBJ_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
|
||||
|
||||
@ -144,7 +244,7 @@ They do not take universal function pointer as argument.
|
||||
You need to provide the detailed `Type` object and the corresponding function pointer.
|
||||
|
||||
```cpp
|
||||
PyObject* f_add(PyObject* lhs, PyObject* rhs){
|
||||
PyObject* f_add(VM* vm, PyObject* lhs, PyObject* rhs){
|
||||
int a = py_cast<int>(vm, lhs);
|
||||
int b = py_cast<int>(vm, rhs);
|
||||
return py_var(vm, a + b);
|
||||
@ -160,4 +260,4 @@ For example, `vm->bind__add__` is preferred over `vm->bind_method<1>(type, "__ad
|
||||
|
||||
### Further reading
|
||||
|
||||
See [linalg.h](https://github.com/blueloveTH/pocketpy/blob/main/src/linalg.h) for a complete example used by `linalg` module.
|
||||
See [random.cpp](https://github.com/blueloveTH/pocketpy/blob/main/src/random.cpp) for an example used by `random` module.
|
||||
@ -6,7 +6,14 @@ label: Coding style guide
|
||||
|
||||
# Coding Style Guide
|
||||
|
||||
## Naming rules
|
||||
|
||||
## 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**
|
||||
|
||||
@ -53,7 +60,7 @@ For macros, use **SNAKE_CASE**
|
||||
#define TEST(x) x+1
|
||||
```
|
||||
|
||||
## Access control
|
||||
### Access control
|
||||
|
||||
Please use python style access control.
|
||||
|
||||
@ -74,7 +81,7 @@ public:
|
||||
|
||||
It does not forbid users to access internal members.
|
||||
|
||||
## Use compact style
|
||||
### Use compact style
|
||||
|
||||
Try to make the code compact if it does not affect readability.
|
||||
|
||||
@ -88,7 +95,7 @@ if(x == 1){
|
||||
}
|
||||
```
|
||||
|
||||
## For `std::shared_ptr<T>`
|
||||
### For `std::shared_ptr<T>`
|
||||
|
||||
Use a `_` suffix to indicate a type is a shared pointer.
|
||||
|
||||
|
||||
@ -39,8 +39,12 @@ These platforms are officially tested.
|
||||
+ Android 64-bit / 32-bit
|
||||
+ iOS 64-bit
|
||||
+ Emscripten 32-bit
|
||||
+ Raspberry Pi 64-bit
|
||||
+ Raspberry Pi OS 64-bit
|
||||
|
||||
## Star the repo
|
||||
|
||||
If you find pocketpy useful, consider [star this repository](https://github.com/blueloveth/pocketpy) (●'◡'●)
|
||||
|
||||
## Sponsor me
|
||||
|
||||
You can sponsor me via [Github Sponsors](https://github.com/sponsors/blueloveTH).
|
||||
You can sponsor me via [Github Sponsors](https://github.com/sponsors/blueloveTH). Your sponsorship will help me develop this project continuously.
|
||||
|
||||
@ -43,6 +43,7 @@ To compile it with your project, these flags must be set:
|
||||
|
||||
+ `--std=c++17` flag must be set
|
||||
+ 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.
|
||||
|
||||
@ -81,6 +81,7 @@ OPCODE(CONTAINS_OP)
|
||||
/**************************/
|
||||
OPCODE(JUMP_ABSOLUTE)
|
||||
OPCODE(POP_JUMP_IF_FALSE)
|
||||
OPCODE(POP_JUMP_IF_TRUE)
|
||||
OPCODE(JUMP_IF_TRUE_OR_POP)
|
||||
OPCODE(JUMP_IF_FALSE_OR_POP)
|
||||
OPCODE(SHORTCUT_IF_FALSE_OR_POP)
|
||||
@ -120,9 +121,9 @@ OPCODE(STORE_CLASS_ATTR)
|
||||
OPCODE(WITH_ENTER)
|
||||
OPCODE(WITH_EXIT)
|
||||
/**************************/
|
||||
OPCODE(ASSERT)
|
||||
OPCODE(EXCEPTION_MATCH)
|
||||
OPCODE(RAISE)
|
||||
OPCODE(RAISE_ASSERT)
|
||||
OPCODE(RE_RAISE)
|
||||
OPCODE(POP_EXCEPTION)
|
||||
/**************************/
|
||||
|
||||
@ -32,7 +32,10 @@ class Pointer(Generic[T], void_p):
|
||||
def __getitem__(self, index: int) -> T: ...
|
||||
def __setitem__(self, index: int, value: T) -> None: ...
|
||||
|
||||
class char_p(Pointer[int]): pass
|
||||
class char_p(Pointer[int]):
|
||||
def read_string(self) -> str: ...
|
||||
def write_string(self, value: str) -> None: ...
|
||||
|
||||
class uchar_p(Pointer[int]): pass
|
||||
class short_p(Pointer[int]): pass
|
||||
class ushort_p(Pointer[int]): pass
|
||||
|
||||
@ -96,14 +96,115 @@ def sorted(iterable, reverse=False, key=None):
|
||||
return a
|
||||
|
||||
##### str #####
|
||||
def __f(self, *args):
|
||||
if '{}' in self:
|
||||
for i in range(len(args)):
|
||||
self = self.replace('{}', str(args[i]), 1)
|
||||
else:
|
||||
for i in range(len(args)):
|
||||
self = self.replace('{'+str(i)+'}', str(args[i]))
|
||||
return self
|
||||
def __f(self: str, *args, **kwargs) -> str:
|
||||
def tokenizeString(s: str):
|
||||
tokens = []
|
||||
L, R = 0,0
|
||||
|
||||
mode = None
|
||||
curArg = 0
|
||||
# lookingForKword = False
|
||||
|
||||
while(R<len(s)):
|
||||
curChar = s[R]
|
||||
nextChar = s[R+1] if R+1<len(s) else ''
|
||||
|
||||
# Invalid case 1: stray '}' encountered, example: "ABCD EFGH {name} IJKL}", "Hello {vv}}", "HELLO {0} WORLD}"
|
||||
if curChar == '}' and nextChar != '}':
|
||||
raise ValueError("Single '}' encountered in format string")
|
||||
|
||||
# Valid Case 1: Escaping case, we escape "{{ or "}}" to be "{" or "}", example: "{{}}", "{{My Name is {0}}}"
|
||||
if (curChar == '{' and nextChar == '{') or (curChar == '}' and nextChar == '}'):
|
||||
|
||||
if (L<R): # Valid Case 1.1: make sure we are not adding empty string
|
||||
tokens.append(s[L:R]) # add the string before the escape
|
||||
|
||||
|
||||
tokens.append(curChar) # Valid Case 1.2: add the escape char
|
||||
L = R+2 # move the left pointer to the next char
|
||||
R = R+2 # move the right pointer to the next char
|
||||
continue
|
||||
|
||||
# Valid Case 2: Regular command line arg case: example: "ABCD EFGH {} IJKL", "{}", "HELLO {} WORLD"
|
||||
elif curChar == '{' and nextChar == '}':
|
||||
if mode is not None and mode != 'auto':
|
||||
# Invalid case 2: mixing automatic and manual field specifications -- example: "ABCD EFGH {name} IJKL {}", "Hello {vv} {}", "HELLO {0} WORLD {}"
|
||||
raise ValueError("Cannot switch from manual field numbering to automatic field specification")
|
||||
|
||||
mode = 'auto'
|
||||
if(L<R): # Valid Case 2.1: make sure we are not adding empty string
|
||||
tokens.append(s[L:R]) # add the string before the special marker for the arg
|
||||
|
||||
tokens.append("{"+str(curArg)+"}") # Valid Case 2.2: add the special marker for the arg
|
||||
curArg+=1 # increment the arg position, this will be used for referencing the arg later
|
||||
|
||||
L = R+2 # move the left pointer to the next char
|
||||
R = R+2 # move the right pointer to the next char
|
||||
continue
|
||||
|
||||
# Valid Case 3: Key-word arg case: example: "ABCD EFGH {name} IJKL", "Hello {vv}", "HELLO {name} WORLD"
|
||||
elif (curChar == '{'):
|
||||
|
||||
if mode is not None and mode != 'manual':
|
||||
# # Invalid case 2: mixing automatic and manual field specifications -- example: "ABCD EFGH {} IJKL {name}", "Hello {} {1}", "HELLO {} WORLD {name}"
|
||||
raise ValueError("Cannot switch from automatic field specification to manual field numbering")
|
||||
|
||||
mode = 'manual'
|
||||
|
||||
if(L<R): # Valid case 3.1: make sure we are not adding empty string
|
||||
tokens.append(s[L:R]) # add the string before the special marker for the arg
|
||||
|
||||
# We look for the end of the keyword
|
||||
kwL = R # Keyword left pointer
|
||||
kwR = R+1 # Keyword right pointer
|
||||
while(kwR<len(s) and s[kwR]!='}'):
|
||||
if s[kwR] == '{': # Invalid case 3: stray '{' encountered, example: "ABCD EFGH {n{ame} IJKL {", "Hello {vv{}}", "HELLO {0} WOR{LD}"
|
||||
raise ValueError("Unexpected '{' in field name")
|
||||
kwR += 1
|
||||
|
||||
# Valid case 3.2: We have successfully found the end of the keyword
|
||||
if kwR<len(s) and s[kwR] == '}':
|
||||
tokens.append(s[kwL:kwR+1]) # add the special marker for the arg
|
||||
L = kwR+1
|
||||
R = kwR+1
|
||||
|
||||
# Invalid case 4: We didn't find the end of the keyword, throw error
|
||||
else:
|
||||
raise ValueError("Expected '}' before end of string")
|
||||
continue
|
||||
|
||||
R = R+1
|
||||
|
||||
|
||||
# Valid case 4: We have reached the end of the string, add the remaining string to the tokens
|
||||
if L<R:
|
||||
tokens.append(s[L:R])
|
||||
|
||||
# print(tokens)
|
||||
return tokens
|
||||
|
||||
tokens = tokenizeString(self)
|
||||
argMap = {}
|
||||
for i, a in enumerate(args):
|
||||
argMap[str(i)] = a
|
||||
final_tokens = []
|
||||
for t in tokens:
|
||||
if t[0] == '{' and t[-1] == '}':
|
||||
key = t[1:-1]
|
||||
argMapVal = argMap.get(key, None)
|
||||
kwargsVal = kwargs.get(key, None)
|
||||
|
||||
if argMapVal is None and kwargsVal is None:
|
||||
raise ValueError("No arg found for token: "+t)
|
||||
elif argMapVal is not None:
|
||||
final_tokens.append(str(argMapVal))
|
||||
else:
|
||||
final_tokens.append(str(kwargsVal))
|
||||
else:
|
||||
final_tokens.append(t)
|
||||
|
||||
return ''.join(final_tokens)
|
||||
|
||||
str.format = __f
|
||||
|
||||
def __f(self, chars=None):
|
||||
|
||||
@ -473,6 +473,9 @@ __NEXT_STEP:;
|
||||
TARGET(POP_JUMP_IF_FALSE)
|
||||
if(!py_bool(POPX())) frame->jump_abs(byte.arg);
|
||||
DISPATCH();
|
||||
TARGET(POP_JUMP_IF_TRUE)
|
||||
if(py_bool(POPX())) frame->jump_abs(byte.arg);
|
||||
DISPATCH();
|
||||
TARGET(JUMP_IF_TRUE_OR_POP)
|
||||
if(py_bool(TOP()) == true) frame->jump_abs(byte.arg);
|
||||
else POP();
|
||||
@ -682,19 +685,6 @@ __NEXT_STEP:;
|
||||
call_method(POPX(), __exit__);
|
||||
DISPATCH();
|
||||
/*****************************************/
|
||||
TARGET(ASSERT) {
|
||||
_0 = TOP();
|
||||
Str msg;
|
||||
if(is_type(_0, tp_tuple)){
|
||||
auto& t = CAST(Tuple&, _0);
|
||||
if(t.size() != 2) ValueError("assert tuple must have 2 elements");
|
||||
_0 = t[0];
|
||||
msg = CAST(Str&, py_str(t[1]));
|
||||
}
|
||||
bool ok = py_bool(_0);
|
||||
POP();
|
||||
if(!ok) _error("AssertionError", msg);
|
||||
} DISPATCH();
|
||||
TARGET(EXCEPTION_MATCH) {
|
||||
const auto& e = CAST(Exception&, TOP());
|
||||
_name = StrName(byte.arg);
|
||||
@ -705,6 +695,14 @@ __NEXT_STEP:;
|
||||
Str msg = _0 == None ? "" : CAST(Str, py_str(_0));
|
||||
_error(StrName(byte.arg), msg);
|
||||
} DISPATCH();
|
||||
TARGET(RAISE_ASSERT)
|
||||
if(byte.arg){
|
||||
_0 = py_str(POPX());
|
||||
_error("AssertionError", CAST(Str, _0));
|
||||
}else{
|
||||
_error("AssertionError", "");
|
||||
}
|
||||
DISPATCH();
|
||||
TARGET(RE_RAISE) _raise(true); DISPATCH();
|
||||
TARGET(POP_EXCEPTION) _last_exception = POPX(); DISPATCH();
|
||||
/*****************************************/
|
||||
@ -760,12 +758,6 @@ __NEXT_STEP:;
|
||||
PK_UNUSED(e);
|
||||
PyObject* obj = POPX();
|
||||
Exception& _e = CAST(Exception&, obj);
|
||||
int actual_ip = frame->_ip;
|
||||
if(_e._ip_on_error >= 0 && _e._code_on_error == (void*)frame->co) actual_ip = _e._ip_on_error;
|
||||
int current_line = frame->co->lines[actual_ip]; // current line
|
||||
auto current_f_name = frame->co->name.sv(); // current function name
|
||||
if(frame->_callable == nullptr) current_f_name = ""; // not in a function
|
||||
_e.st_push(frame->co->src->snapshot(current_line, nullptr, current_f_name));
|
||||
_pop_frame();
|
||||
if(callstack.empty()){
|
||||
#if PK_DEBUG_FULL_EXCEPTION
|
||||
|
||||
16
src/cffi.cpp
16
src/cffi.cpp
@ -226,6 +226,22 @@ void add_module_c(VM* vm){
|
||||
BIND_PRIMITIVE(bool, "bool")
|
||||
|
||||
#undef BIND_PRIMITIVE
|
||||
|
||||
PyObject* char_p_t = mod->attr("char_p");
|
||||
vm->bind(char_p_t, "read_string(self) -> str", [](VM* vm, ArgsView args){
|
||||
VoidP& voidp = PK_OBJ_GET(VoidP, args[0]);
|
||||
const char* target = (const char*)voidp.ptr;
|
||||
return VAR(target);
|
||||
});
|
||||
|
||||
vm->bind(char_p_t, "write_string(self, value: str)", [](VM* vm, ArgsView args){
|
||||
VoidP& voidp = PK_OBJ_GET(VoidP, args[0]);
|
||||
std::string_view sv = CAST(Str&, args[1]).sv();
|
||||
char* target = (char*)voidp.ptr;
|
||||
memcpy(target, sv.data(), sv.size());
|
||||
target[sv.size()] = '\0';
|
||||
return vm->None;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace pkpy
|
||||
@ -775,7 +775,10 @@ __EAT_DOTS_END:
|
||||
case TK("++"):{
|
||||
consume(TK("@id"));
|
||||
StrName name(prev().sv());
|
||||
switch(name_scope()){
|
||||
NameScope scope = name_scope();
|
||||
bool is_global = ctx()->global_names.count(name.sv());
|
||||
if(is_global) scope = NAME_GLOBAL;
|
||||
switch(scope){
|
||||
case NAME_LOCAL:
|
||||
ctx()->emit(OP_INC_FAST, ctx()->add_varname(name), prev().line);
|
||||
break;
|
||||
@ -802,11 +805,19 @@ __EAT_DOTS_END:
|
||||
consume_end_stmt();
|
||||
break;
|
||||
}
|
||||
case TK("assert"):
|
||||
EXPR_TUPLE(false);
|
||||
ctx()->emit(OP_ASSERT, BC_NOARG, kw_line);
|
||||
case TK("assert"):{
|
||||
EXPR(false); // condition
|
||||
int index = ctx()->emit(OP_POP_JUMP_IF_TRUE, BC_NOARG, kw_line);
|
||||
int has_msg = 0;
|
||||
if(match(TK(","))){
|
||||
EXPR(false); // message
|
||||
has_msg = 1;
|
||||
}
|
||||
ctx()->emit(OP_RAISE_ASSERT, has_msg, kw_line);
|
||||
ctx()->patch_jump(index);
|
||||
consume_end_stmt();
|
||||
break;
|
||||
}
|
||||
case TK("global"):
|
||||
do {
|
||||
consume(TK("@id"));
|
||||
|
||||
18
src/vm.cpp
18
src/vm.cpp
@ -1082,13 +1082,21 @@ void VM::_error(Exception e){
|
||||
}
|
||||
|
||||
void VM::_raise(bool re_raise){
|
||||
Frame* top = top_frame().get();
|
||||
Frame* frame = top_frame().get();
|
||||
Exception& e = PK_OBJ_GET(Exception, s_data.top());
|
||||
if(!re_raise){
|
||||
Exception& e = PK_OBJ_GET(Exception, s_data.top());
|
||||
e._ip_on_error = top->_ip;
|
||||
e._code_on_error = (void*)top->co;
|
||||
e._ip_on_error = frame->_ip;
|
||||
e._code_on_error = (void*)frame->co;
|
||||
}
|
||||
bool ok = top->jump_to_exception_handler();
|
||||
bool ok = frame->jump_to_exception_handler();
|
||||
|
||||
int actual_ip = frame->_ip;
|
||||
if(e._ip_on_error >= 0 && e._code_on_error == (void*)frame->co) actual_ip = e._ip_on_error;
|
||||
int current_line = frame->co->lines[actual_ip]; // current line
|
||||
auto current_f_name = frame->co->name.sv(); // current function name
|
||||
if(frame->_callable == nullptr) current_f_name = ""; // not in a function
|
||||
e.st_push(frame->co->src->snapshot(current_line, nullptr, current_f_name));
|
||||
|
||||
if(ok) throw HandledException();
|
||||
else throw UnhandledException();
|
||||
}
|
||||
|
||||
@ -97,6 +97,19 @@ assert "{0} {1} {2}".format("I", "love", "Python") == "I love Python"
|
||||
assert "{2} {1} {0}".format("I", "love", "Python") == "Python love I"
|
||||
assert "{0}{1}{0}".format("abra", "cad") == "abracadabra"
|
||||
|
||||
assert "{k}={v}".format(k="key", v="value") == "key=value"
|
||||
assert "{k}={k}".format(k="key") == "key=key"
|
||||
assert "{0}={1}".format('{0}', '{1}') == "{0}={1}"
|
||||
assert "{{{0}}}".format(1) == "{1}"
|
||||
assert "{0}{1}{1}".format(1, 2, 3) == "122"
|
||||
try:
|
||||
"{0}={1}}".format(1, 2)
|
||||
exit(1)
|
||||
except ValueError:
|
||||
pass
|
||||
assert "{{{}xxx{}x}}".format(1, 2) == "{1xxx2x}"
|
||||
assert "{{abc}}".format() == "{abc}"
|
||||
|
||||
# 3rd slice
|
||||
a = "Hello, World!"
|
||||
assert a[::-1] == "!dlroW ,olleH"
|
||||
|
||||
@ -26,3 +26,33 @@ assert ceil(1.2) == 2
|
||||
assert ceil(-1.2) == -1
|
||||
|
||||
assert isclose(sqrt(4), 2.0)
|
||||
|
||||
import math
|
||||
|
||||
# test fsum
|
||||
assert math.fsum([0.1] * 10) == 1.0
|
||||
|
||||
# test gcd
|
||||
assert math.gcd(10, 5) == 5
|
||||
assert math.gcd(10, 6) == 2
|
||||
assert math.gcd(10, 7) == 1
|
||||
assert math.gcd(10, 10) == 10
|
||||
assert math.gcd(-10, 10) == 10
|
||||
|
||||
# test modf
|
||||
x, y = math.modf(1.5)
|
||||
assert isclose(x, 0.5)
|
||||
assert isclose(y, 1.0)
|
||||
|
||||
x, y = math.modf(-1.5)
|
||||
assert isclose(x, -0.5)
|
||||
assert isclose(y, -1.0)
|
||||
|
||||
# test factorial
|
||||
assert math.factorial(0) == 1
|
||||
assert math.factorial(1) == 1
|
||||
assert math.factorial(2) == 2
|
||||
assert math.factorial(3) == 6
|
||||
assert math.factorial(4) == 24
|
||||
assert math.factorial(5) == 120
|
||||
|
||||
|
||||
@ -50,3 +50,15 @@ a = Vec2(1, 2)
|
||||
assert isinstance(a, c.struct)
|
||||
assert type(a) is Vec2
|
||||
assert repr(a) == "Vec2(1.0, 2.0)"
|
||||
|
||||
a = c.struct(10)
|
||||
p = c.p_cast(a.addr(), c.char_p)
|
||||
p.write_string("Hello!")
|
||||
assert p[0] == ord("H")
|
||||
assert p[1] == ord("e")
|
||||
assert p[2] == ord("l")
|
||||
assert p[3] == ord("l")
|
||||
assert p[4] == ord("o")
|
||||
assert p[5] == ord("!")
|
||||
assert p[6] == 0
|
||||
assert p.read_string() == "Hello!"
|
||||
|
||||
@ -16,6 +16,15 @@ a = {
|
||||
|
||||
import json
|
||||
|
||||
assert json.loads("1") == 1
|
||||
assert json.loads('"1"') == "1"
|
||||
assert json.loads("0.0") == 0.0
|
||||
assert json.loads("[1, 2]") == [1, 2]
|
||||
assert json.loads("null") == None
|
||||
assert json.loads("true") == True
|
||||
assert json.loads("false") == False
|
||||
assert json.loads("{}") == {}
|
||||
|
||||
_j = json.dumps(a)
|
||||
_a = json.loads(_j)
|
||||
|
||||
|
||||
@ -327,23 +327,12 @@ assert result_mat == correct_result_mat
|
||||
|
||||
# test determinant
|
||||
test_mat_copy = test_mat.copy()
|
||||
list_mat = [[0,0,0], [0,0,0], [0,0,0]]
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
list_mat[i][j] = test_mat[i, j]
|
||||
determinant = list_mat[0][0]*(list_mat[1][1]*list_mat[2][2] - list_mat[1][2]*list_mat[2][1]) - list_mat[0][1]*(list_mat[1][0]*list_mat[2][2] - list_mat[1][2]*list_mat[2][0]) + list_mat[0][2]*(list_mat[1][0]*list_mat[2][1] - list_mat[1][1]*list_mat[2][0])
|
||||
_0 = determinant
|
||||
_1 = test_mat_copy.determinant()
|
||||
_0, _1 = round(_0, 2), round(_1, 2)
|
||||
assert (_0 == _1), f'{_0} != {_1}'
|
||||
|
||||
|
||||
test_mat_copy.determinant()
|
||||
|
||||
# test __repr__
|
||||
assert str(static_test_mat_float) == 'mat3x3([[7.2642, -5.4322, 1.8765],\n [-2.4911, 8.9897, -0.7169],\n [9.5580, -3.3363, 4.9514]])'
|
||||
assert str(static_test_mat_int) == 'mat3x3([[1.0000, 2.0000, 3.0000],\n [4.0000, 5.0000, 6.0000],\n [7.0000, 8.0000, 9.0000]])'
|
||||
|
||||
|
||||
# test __getnewargs__
|
||||
test_mat_copy = test_mat.copy()
|
||||
element_value_list = [getattr(test_mat, attr) for attr in element_name_list]
|
||||
|
||||
@ -6,5 +6,9 @@ try:
|
||||
except KeyError:
|
||||
s = traceback.format_exc()
|
||||
|
||||
assert s == r'''Traceback (most recent call last):
|
||||
ok = s == '''Traceback (most recent call last):
|
||||
File "80_traceback.py", line 5
|
||||
b = a[6]
|
||||
KeyError: 6'''
|
||||
|
||||
assert ok, s
|
||||
@ -70,3 +70,12 @@ try:
|
||||
exit(1)
|
||||
except UnboundLocalError:
|
||||
pass
|
||||
|
||||
|
||||
g = 1
|
||||
def f():
|
||||
global g
|
||||
++g
|
||||
|
||||
f(); f()
|
||||
assert g == 3
|
||||
Loading…
x
Reference in New Issue
Block a user