This commit is contained in:
Pranav 2023-10-10 16:49:59 +05:30
commit 9c6119d2f1
24 changed files with 457 additions and 127 deletions

View File

@ -1,5 +1,15 @@
name: build name: build
on: [push, pull_request] on:
push:
paths-ignore:
- 'docs/**'
- 'web/**'
- '**.md'
pull_request:
paths-ignore:
- 'docs/**'
- 'web/**'
- '**.md'
jobs: jobs:
build_win: build_win:
runs-on: windows-latest runs-on: windows-latest

View File

@ -21,19 +21,12 @@ if(NOT ${PREBUILD_RESULT} EQUAL 0)
message(FATAL_ERROR "prebuild.py: ${PREBUILD_RESULT}") message(FATAL_ERROR "prebuild.py: ${PREBUILD_RESULT}")
endif() endif()
if(MSVC)
add_compile_options("/utf-8")
endif()
if(EMSCRIPTEN) if(EMSCRIPTEN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -O3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
elseif(MSVC) elseif(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /utf-8") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /utf-8 /O2")
set(CMAKE_CXX_FLAGS_RELEASE "/O2")
else() else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -O2")
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
endif() endif()
include_directories(${CMAKE_CURRENT_LIST_DIR}/include) include_directories(${CMAKE_CURRENT_LIST_DIR}/include)

View File

@ -38,7 +38,7 @@ These platforms are officially tested.
+ Android 64-bit / 32-bit + Android 64-bit / 32-bit
+ iOS 64-bit + iOS 64-bit
+ Emscripten 32-bit + Emscripten 32-bit
+ Raspberry Pi 64-bit + Raspberry Pi OS 64-bit
## Quick Start ## Quick Start
@ -56,11 +56,12 @@ To compile it with your project, these flags must be set:
+ `--std=c++17` flag must be set + `--std=c++17` flag must be set
+ Exception must be enabled + Exception must be enabled
+ For MSVC, `/utf-8` flag must be set
For development build on Linux, use this snippet. For development build on Linux, use this snippet.
```bash ```bash
# prerequisites # prerequisites
sudo apt-get install libc++-dev libc++abi-dev clang++ sudo apt-get install libc++-dev libc++abi-dev clang
# build the repo # build the repo
bash build.sh bash build.sh
# unittest # unittest
@ -114,26 +115,37 @@ for a quick overview of the supported features.
| Name | Example | Supported | | Name | Example | Supported |
| --------------- | ------------------------------- | --------- | | --------------- | ------------------------------- | --------- |
| If Else | `if..else..elif` | YES | | If Else | `if..else..elif` | ✅ |
| Loop | `for/while/break/continue` | YES | | Loop | `for/while/break/continue` | ✅ |
| Function | `def f(x,*args,y=1):` | YES | | Function | `def f(x,*args,y=1):` | ✅ |
| Subclass | `class A(B):` | YES | | Subclass | `class A(B):` | ✅ |
| List | `[1, 2, 'a']` | YES | | List | `[1, 2, 'a']` | ✅ |
| ListComp | `[i for i in range(5)]` | YES | | ListComp | `[i for i in range(5)]` | ✅ |
| Slice | `a[1:2], a[:2], a[1:]` | YES | | Slice | `a[1:2], a[:2], a[1:]` | ✅ |
| Tuple | `(1, 2, 'a')` | YES | | Tuple | `(1, 2, 'a')` | ✅ |
| Dict | `{'a': 1, 'b': 2}` | YES | | Dict | `{'a': 1, 'b': 2}` | ✅ |
| F-String | `f'value is {x}'` | YES | | F-String | `f'value is {x}'` | ✅ |
| Unpacking | `a, b = 1, 2` | YES | | Unpacking | `a, b = 1, 2` | ✅ |
| Star Unpacking | `a, *b = [1, 2, 3]` | YES | | Star Unpacking | `a, *b = [1, 2, 3]` | ✅ |
| Exception | `raise/try..catch` | YES | | Exception | `raise/try..catch` | ✅ |
| Dynamic Code | `eval()/exec()` | YES | | Dynamic Code | `eval()/exec()` | ✅ |
| Reflection | `hasattr()/getattr()/setattr()` | YES | | Reflection | `hasattr()/getattr()/setattr()` | ✅ |
| Import | `import/from..import` | YES | | Import | `import/from..import` | ✅ |
| Context Block | `with <expr> as <id>:` | YES | | Context Block | `with <expr> as <id>:` | ✅ |
| Type Annotation | `def f(a:int, b:float=1)` | YES | | Type Annotation | `def f(a:int, b:float=1)` | ✅ |
| Generator | `yield i` | YES | | Generator | `yield i` | ✅ |
| Decorator | `@cache` | YES | | 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 ## Contribution
@ -146,7 +158,11 @@ All kinds of contributions are welcome.
- any suggestions - any suggestions
- any questions - 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 ## Reference

View File

@ -27,7 +27,7 @@ pkpy 支持任何拥有 C++17 编译器的平台。
+ Android 64-bit / 32-bit + Android 64-bit / 32-bit
+ iOS 64-bit + iOS 64-bit
+ Emscripten 32-bit + Emscripten 32-bit
+ Raspberry Pi 64-bit + Raspberry Pi OS 64-bit
## 快速上手 ## 快速上手
@ -76,25 +76,25 @@ int main(){
| 特性 | 示例 | 支持 | | 特性 | 示例 | 支持 |
| ------------ | ------------------------------- | ---- | | ------------ | ------------------------------- | ---- |
| 分支 | `if..else..elif` | YES | | 分支 | `if..else..elif` | |
| 循环 | `for/while/break/continue` | YES | | 循环 | `for/while/break/continue` | |
| 函数 | `def f(x,*args,y=1):` | YES | | 函数 | `def f(x,*args,y=1):` | |
| 类与继承 | `class A(B):` | YES | | 类与继承 | `class A(B):` | |
| 列表 | `[1, 2, 'a']` | YES | | 列表 | `[1, 2, 'a']` | |
| 列表生成式 | `[i for i in range(5)]` | YES | | 列表生成式 | `[i for i in range(5)]` | |
| 切片 | `a[1:2], a[:2], a[1:]` | YES | | 切片 | `a[1:2], a[:2], a[1:]` | |
| 元组 | `(1, 2, 'a')` | YES | | 元组 | `(1, 2, 'a')` | |
| 字典 | `{'a': 1, 'b': 2}` | YES | | 字典 | `{'a': 1, 'b': 2}` | |
| 格式化字符串 | `f'value is {x}'` | YES | | 格式化字符串 | `f'value is {x}'` | |
| 序列解包 | `a, b = 1, 2` | YES | | 序列解包 | `a, b = 1, 2` | |
| 异常 | `raise/try..catch` | YES | | 异常 | `raise/try..catch` | |
| 动态分发 | `eval()/exec()` | YES | | 动态分发 | `eval()/exec()` | |
| 反射 | `hasattr()/getattr()/setattr()` | YES | | 反射 | `hasattr()/getattr()/setattr()` | |
| 导入模块 | `import/from..import` | YES | | 导入模块 | `import/from..import` | |
| 上下文管理器 | `with <expr> as <id>:` | YES | | 上下文管理器 | `with <expr> as <id>:` | |
| 类型标注 | `def f(a:int, b:float=1)` | YES | | 类型标注 | `def f(a:int, b:float=1)` | ✅ |
| 生成器 | `yield i` | YES | | 生成器 | `yield i` | |
| 装饰器 | `@cache` | YES | | 装饰器 | `@cache` | ✅ |
## 参考 ## 参考

View File

@ -1,9 +1,12 @@
if (Test-Path build) { if (Test-Path build) {
Remove-Item -Recurse -Force build Remove-Item -Recurse -Force build
} }
mkdir build
cd build New-Item -ItemType Directory -Path build
Push-Location build
cmake .. cmake ..
cmake --build . --config Release 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 ".."

View File

@ -10,24 +10,22 @@ fi
# Check if clang++ is installed # Check if clang++ is installed
if ! type -P clang++ >/dev/null 2>&1; then if ! type -P clang++ >/dev/null 2>&1; then
echo "clang++ is required and not installed. Kindly install it." 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 exit 1
fi 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 "It takes a moment to finish building."
echo "" echo ""
echo "> Running prebuild.py... " echo "> Running prebuild.py... "
python3 prebuild.py python3 prebuild.py
# echo -n "Finding source files... "
SRC=$(find src/ -name "*.cpp") SRC=$(find src/ -name "*.cpp")
# echo "Done"
echo "> Compiling and linking source files... " 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 if [[ "$OSTYPE" == "darwin"* ]]; then
LIB_EXTENSION=".dylib" LIB_EXTENSION=".dylib"
FLAGS="$FLAGS -undefined dynamic_lookup" 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 # compile main.cpp and link to libpocketpy.so
echo "> Compiling main.cpp and linking to libpocketpy$LIB_EXTENSION..." 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 if [ $? -eq 0 ]; then
echo "Build completed successfully. To use pocketpy, run : ./main" echo "Build completed. Type \"./main\" to enter REPL."
else else
echo "Build failed." echo "Build failed."
exit 1 exit 1

View File

@ -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 ### Bind a struct
Assume you have a struct `Point` declared as follows. 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 ### 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. You need to provide the detailed `Type` object and the corresponding function pointer.
```cpp ```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 a = py_cast<int>(vm, lhs);
int b = py_cast<int>(vm, rhs); int b = py_cast<int>(vm, rhs);
return py_var(vm, a + b); 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 ### 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.

View File

@ -6,7 +6,14 @@ label: Coding style guide
# 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** For class names, always use **PascalCase**
@ -53,7 +60,7 @@ For macros, use **SNAKE_CASE**
#define TEST(x) x+1 #define TEST(x) x+1
``` ```
## Access control ### Access control
Please use python style access control. Please use python style access control.
@ -74,7 +81,7 @@ public:
It does not forbid users to access internal members. 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. 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. Use a `_` suffix to indicate a type is a shared pointer.

View File

@ -39,8 +39,12 @@ These platforms are officially tested.
+ Android 64-bit / 32-bit + Android 64-bit / 32-bit
+ iOS 64-bit + iOS 64-bit
+ Emscripten 32-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 ## 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.

View File

@ -43,6 +43,7 @@ To compile it with your project, these flags must be set:
+ `--std=c++17` flag must be set + `--std=c++17` flag must be set
+ Exception 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. For emscripten, you must enable exceptions to make pocketpy work properly.
See https://emscripten.org/docs/porting/exceptions.html. See https://emscripten.org/docs/porting/exceptions.html.

View File

@ -81,6 +81,7 @@ OPCODE(CONTAINS_OP)
/**************************/ /**************************/
OPCODE(JUMP_ABSOLUTE) OPCODE(JUMP_ABSOLUTE)
OPCODE(POP_JUMP_IF_FALSE) OPCODE(POP_JUMP_IF_FALSE)
OPCODE(POP_JUMP_IF_TRUE)
OPCODE(JUMP_IF_TRUE_OR_POP) OPCODE(JUMP_IF_TRUE_OR_POP)
OPCODE(JUMP_IF_FALSE_OR_POP) OPCODE(JUMP_IF_FALSE_OR_POP)
OPCODE(SHORTCUT_IF_FALSE_OR_POP) OPCODE(SHORTCUT_IF_FALSE_OR_POP)
@ -120,9 +121,9 @@ OPCODE(STORE_CLASS_ATTR)
OPCODE(WITH_ENTER) OPCODE(WITH_ENTER)
OPCODE(WITH_EXIT) OPCODE(WITH_EXIT)
/**************************/ /**************************/
OPCODE(ASSERT)
OPCODE(EXCEPTION_MATCH) OPCODE(EXCEPTION_MATCH)
OPCODE(RAISE) OPCODE(RAISE)
OPCODE(RAISE_ASSERT)
OPCODE(RE_RAISE) OPCODE(RE_RAISE)
OPCODE(POP_EXCEPTION) OPCODE(POP_EXCEPTION)
/**************************/ /**************************/

View File

@ -32,7 +32,10 @@ class Pointer(Generic[T], void_p):
def __getitem__(self, index: int) -> T: ... def __getitem__(self, index: int) -> T: ...
def __setitem__(self, index: int, value: T) -> None: ... 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 uchar_p(Pointer[int]): pass
class short_p(Pointer[int]): pass class short_p(Pointer[int]): pass
class ushort_p(Pointer[int]): pass class ushort_p(Pointer[int]): pass

View File

@ -96,14 +96,115 @@ def sorted(iterable, reverse=False, key=None):
return a return a
##### str ##### ##### str #####
def __f(self, *args): def __f(self: str, *args, **kwargs) -> str:
if '{}' in self: def tokenizeString(s: str):
for i in range(len(args)): tokens = []
self = self.replace('{}', str(args[i]), 1) L, R = 0,0
else:
for i in range(len(args)): mode = None
self = self.replace('{'+str(i)+'}', str(args[i])) curArg = 0
return self # 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 str.format = __f
def __f(self, chars=None): def __f(self, chars=None):

View File

@ -473,6 +473,9 @@ __NEXT_STEP:;
TARGET(POP_JUMP_IF_FALSE) TARGET(POP_JUMP_IF_FALSE)
if(!py_bool(POPX())) frame->jump_abs(byte.arg); if(!py_bool(POPX())) frame->jump_abs(byte.arg);
DISPATCH(); DISPATCH();
TARGET(POP_JUMP_IF_TRUE)
if(py_bool(POPX())) frame->jump_abs(byte.arg);
DISPATCH();
TARGET(JUMP_IF_TRUE_OR_POP) TARGET(JUMP_IF_TRUE_OR_POP)
if(py_bool(TOP()) == true) frame->jump_abs(byte.arg); if(py_bool(TOP()) == true) frame->jump_abs(byte.arg);
else POP(); else POP();
@ -682,19 +685,6 @@ __NEXT_STEP:;
call_method(POPX(), __exit__); call_method(POPX(), __exit__);
DISPATCH(); 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) { TARGET(EXCEPTION_MATCH) {
const auto& e = CAST(Exception&, TOP()); const auto& e = CAST(Exception&, TOP());
_name = StrName(byte.arg); _name = StrName(byte.arg);
@ -705,6 +695,14 @@ __NEXT_STEP:;
Str msg = _0 == None ? "" : CAST(Str, py_str(_0)); Str msg = _0 == None ? "" : CAST(Str, py_str(_0));
_error(StrName(byte.arg), msg); _error(StrName(byte.arg), msg);
} DISPATCH(); } 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(RE_RAISE) _raise(true); DISPATCH();
TARGET(POP_EXCEPTION) _last_exception = POPX(); DISPATCH(); TARGET(POP_EXCEPTION) _last_exception = POPX(); DISPATCH();
/*****************************************/ /*****************************************/
@ -760,12 +758,6 @@ __NEXT_STEP:;
PK_UNUSED(e); PK_UNUSED(e);
PyObject* obj = POPX(); PyObject* obj = POPX();
Exception& _e = CAST(Exception&, obj); 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(); _pop_frame();
if(callstack.empty()){ if(callstack.empty()){
#if PK_DEBUG_FULL_EXCEPTION #if PK_DEBUG_FULL_EXCEPTION

View File

@ -226,6 +226,22 @@ void add_module_c(VM* vm){
BIND_PRIMITIVE(bool, "bool") BIND_PRIMITIVE(bool, "bool")
#undef BIND_PRIMITIVE #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 } // namespace pkpy

View File

@ -775,7 +775,10 @@ __EAT_DOTS_END:
case TK("++"):{ case TK("++"):{
consume(TK("@id")); consume(TK("@id"));
StrName name(prev().sv()); 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: case NAME_LOCAL:
ctx()->emit(OP_INC_FAST, ctx()->add_varname(name), prev().line); ctx()->emit(OP_INC_FAST, ctx()->add_varname(name), prev().line);
break; break;
@ -802,11 +805,19 @@ __EAT_DOTS_END:
consume_end_stmt(); consume_end_stmt();
break; break;
} }
case TK("assert"): case TK("assert"):{
EXPR_TUPLE(false); EXPR(false); // condition
ctx()->emit(OP_ASSERT, BC_NOARG, kw_line); 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(); consume_end_stmt();
break; break;
}
case TK("global"): case TK("global"):
do { do {
consume(TK("@id")); consume(TK("@id"));

View File

@ -1082,13 +1082,21 @@ void VM::_error(Exception e){
} }
void VM::_raise(bool re_raise){ 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){ if(!re_raise){
Exception& e = PK_OBJ_GET(Exception, s_data.top()); e._ip_on_error = frame->_ip;
e._ip_on_error = top->_ip; e._code_on_error = (void*)frame->co;
e._code_on_error = (void*)top->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(); if(ok) throw HandledException();
else throw UnhandledException(); else throw UnhandledException();
} }

View File

@ -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 "{2} {1} {0}".format("I", "love", "Python") == "Python love I"
assert "{0}{1}{0}".format("abra", "cad") == "abracadabra" 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 # 3rd slice
a = "Hello, World!" a = "Hello, World!"
assert a[::-1] == "!dlroW ,olleH" assert a[::-1] == "!dlroW ,olleH"

View File

@ -25,4 +25,34 @@ assert floor(-1.2) == -2
assert ceil(1.2) == 2 assert ceil(1.2) == 2
assert ceil(-1.2) == -1 assert ceil(-1.2) == -1
assert isclose(sqrt(4), 2.0) 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

View File

@ -49,4 +49,16 @@ class Vec2(c.struct):
a = Vec2(1, 2) a = Vec2(1, 2)
assert isinstance(a, c.struct) assert isinstance(a, c.struct)
assert type(a) is Vec2 assert type(a) is Vec2
assert repr(a) == "Vec2(1.0, 2.0)" 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!"

View File

@ -16,6 +16,15 @@ a = {
import json 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) _j = json.dumps(a)
_a = json.loads(_j) _a = json.loads(_j)

View File

@ -327,23 +327,12 @@ assert result_mat == correct_result_mat
# test determinant # test determinant
test_mat_copy = test_mat.copy() test_mat_copy = test_mat.copy()
list_mat = [[0,0,0], [0,0,0], [0,0,0]] test_mat_copy.determinant()
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 __repr__ # 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_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]])' 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 __getnewargs__
test_mat_copy = test_mat.copy() test_mat_copy = test_mat.copy()
element_value_list = [getattr(test_mat, attr) for attr in element_name_list] element_value_list = [getattr(test_mat, attr) for attr in element_name_list]

View File

@ -6,5 +6,9 @@ try:
except KeyError: except KeyError:
s = traceback.format_exc() s = traceback.format_exc()
assert s == r'''Traceback (most recent call last): ok = s == '''Traceback (most recent call last):
KeyError: 6''' File "80_traceback.py", line 5
b = a[6]
KeyError: 6'''
assert ok, s

View File

@ -70,3 +70,12 @@ try:
exit(1) exit(1)
except UnboundLocalError: except UnboundLocalError:
pass pass
g = 1
def f():
global g
++g
f(); f()
assert g == 3