diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 99bddbf9..4b48e6f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f05b5b5..67a0e8a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index f0063c8d..005df4fd 100644 --- a/README.md +++ b/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 as :` | 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 as :` | ✅ | +| 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 diff --git a/README_zh.md b/README_zh.md index 9e1394b8..007ab2f4 100644 --- a/README_zh.md +++ b/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 as :` | 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 as :` | ✅ | +| 类型标注 | `def f(a:int, b:float=1)` | ✅ | +| 生成器 | `yield i` | ✅ | +| 装饰器 | `@cache` | ✅ | ## 参考 diff --git a/build.ps1 b/build.ps1 index 3b794a76..af980cfe 100644 --- a/build.ps1 +++ b/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 ../ \ No newline at end of file + +Copy-Item "Release\main.exe" -Destination ".." # Note: NTFS uses backslash (\) instead of slashes (*nix, /) +Copy-Item "Release\pocketpy.dll" -Destination ".." diff --git a/build.sh b/build.sh index 00290fe2..12648298 100644 --- a/build.sh +++ b/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 diff --git a/docs/bindings.md b/docs/bindings.md index 33968d5c..98ad9742 100644 --- a/docs/bindings.md +++ b/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(_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(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(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 b; + // ... +} +``` + +Add a magic method `_gc_mark() const` to your custom type. + +```cpp +struct Container{ + PY_CLASS(Container, builtins, Container) + + PyObject* a; + std::vector 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(vm, lhs); int b = py_cast(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. \ No newline at end of file +See [random.cpp](https://github.com/blueloveTH/pocketpy/blob/main/src/random.cpp) for an example used by `random` module. \ No newline at end of file diff --git a/docs/coding_style_guide.md b/docs/coding_style_guide.md index 647ba259..bb72487f 100644 --- a/docs/coding_style_guide.md +++ b/docs/coding_style_guide.md @@ -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` +### For `std::shared_ptr` Use a `_` suffix to indicate a type is a shared pointer. diff --git a/docs/index.md b/docs/index.md index 673dc2ee..5fbf82e0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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. diff --git a/docs/quick-start/installation.md b/docs/quick-start/installation.md index 4332c228..9a0f4f38 100644 --- a/docs/quick-start/installation.md +++ b/docs/quick-start/installation.md @@ -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. diff --git a/include/pocketpy/opcodes.h b/include/pocketpy/opcodes.h index 1d34506b..7ce73324 100644 --- a/include/pocketpy/opcodes.h +++ b/include/pocketpy/opcodes.h @@ -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) /**************************/ diff --git a/include/typings/c.pyi b/include/typings/c.pyi index 1ab69684..06b1345f 100644 --- a/include/typings/c.pyi +++ b/include/typings/c.pyi @@ -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 diff --git a/python/builtins.py b/python/builtins.py index 4abb8187..436cd39c 100644 --- a/python/builtins.py +++ b/python/builtins.py @@ -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(Rjump_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 diff --git a/src/cffi.cpp b/src/cffi.cpp index 2d23bdb1..7bdf6023 100644 --- a/src/cffi.cpp +++ b/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 \ No newline at end of file diff --git a/src/compiler.cpp b/src/compiler.cpp index 86f806fd..879ca10c 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -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")); diff --git a/src/vm.cpp b/src/vm.cpp index 5a016938..5ee9913d 100644 --- a/src/vm.cpp +++ b/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(); } diff --git a/tests/04_str.py b/tests/04_str.py index dd4a1587..6a2e1a44 100644 --- a/tests/04_str.py +++ b/tests/04_str.py @@ -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" diff --git a/tests/70_math.py b/tests/70_math.py index 0545aabc..4dbcce67 100644 --- a/tests/70_math.py +++ b/tests/70_math.py @@ -25,4 +25,34 @@ assert floor(-1.2) == -2 assert ceil(1.2) == 2 assert ceil(-1.2) == -1 -assert isclose(sqrt(4), 2.0) \ No newline at end of file +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 + diff --git a/tests/80_c.py b/tests/80_c.py index f61484c6..55d4d854 100644 --- a/tests/80_c.py +++ b/tests/80_c.py @@ -49,4 +49,16 @@ class Vec2(c.struct): a = Vec2(1, 2) assert isinstance(a, c.struct) assert type(a) is Vec2 -assert repr(a) == "Vec2(1.0, 2.0)" \ No newline at end of file +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!" diff --git a/tests/80_json.py b/tests/80_json.py index d821ab92..1e0f1ccc 100644 --- a/tests/80_json.py +++ b/tests/80_json.py @@ -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) diff --git a/tests/80_linalg.py b/tests/80_linalg.py index 0b6eae2c..77c2aee6 100644 --- a/tests/80_linalg.py +++ b/tests/80_linalg.py @@ -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] diff --git a/tests/80_traceback.py b/tests/80_traceback.py index 25fbfec7..b1df66fd 100644 --- a/tests/80_traceback.py +++ b/tests/80_traceback.py @@ -6,5 +6,9 @@ try: except KeyError: s = traceback.format_exc() -assert s == r'''Traceback (most recent call last): -KeyError: 6''' \ No newline at end of file +ok = s == '''Traceback (most recent call last): + File "80_traceback.py", line 5 + b = a[6] +KeyError: 6''' + +assert ok, s \ No newline at end of file diff --git a/tests/99_bugs.py b/tests/99_bugs.py index c86a8813..07f7949a 100644 --- a/tests/99_bugs.py +++ b/tests/99_bugs.py @@ -70,3 +70,12 @@ try: exit(1) except UnboundLocalError: pass + + +g = 1 +def f(): + global g + ++g + +f(); f() +assert g == 3 \ No newline at end of file