From ac8f4a1c2db55a184f1e4f11ee0ef81f50695475 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Sun, 25 Aug 2024 01:42:08 +0800 Subject: [PATCH] some fix --- CMakeLists.txt | 10 +- README.md | 5 +- amalgamate.py | 2 +- docs/bindings-cpp.md | 291 +++++++++++++++++++++++++++++++ docs/bindings.md | 10 +- docs/features/basic.md | 3 - docs/features/debugging.md | 2 +- docs/features/differences.md | 20 +-- docs/features/precompile.md | 145 --------------- docs/features/ub.md | 2 +- docs/quick-start.md | 7 +- include/pocketpy/common/str.h | 2 + include/pocketpy/linalg.h | 18 +- include/pocketpy/pocketpy.h | 1 - include/typings/cjson.pyi | 2 - include/typings/linalg.pyi | 10 ++ src/interpreter/ceval.c | 5 +- src/public/py_slice.c | 33 ++++ tests/05_list.py | 20 --- tests/28_exception.py | 2 +- tests/{99_bugs.py => 95_bugs.py} | 0 tests/{99_dis.py => 95_dis.py} | 0 tests/99_extras.py | 49 ++++++ 23 files changed, 420 insertions(+), 219 deletions(-) create mode 100644 docs/bindings-cpp.md delete mode 100644 docs/features/precompile.md delete mode 100644 include/typings/cjson.pyi rename tests/{99_bugs.py => 95_bugs.py} (100%) rename tests/{99_dis.py => 95_dis.py} (100%) create mode 100644 tests/99_extras.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f74012c..0c17d50f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,12 +12,9 @@ check_ipo_supported(RESULT result) if(result AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) else() - message(WARNING ">> IPO disabled. You will get very poor performance!!") - message(WARNING ">> IPO disabled. You will get very poor performance!!") - message(WARNING ">> IPO disabled. You will get very poor performance!!") + message(WARNING ">> IPO disabled. You will not get the best performance.") endif() - if(MSVC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /jumptablerdata /GS-") add_compile_options(/wd4267 /wd4244) @@ -47,11 +44,6 @@ if(PK_ENABLE_OS) add_definitions(-DPK_ENABLE_OS=1) endif() -option(PK_ENABLE_PROFILER "" OFF) -if(PK_ENABLE_PROFILER) - add_definitions(-DPK_ENABLE_PROFILER=1) -endif() - # PK_IS_MAIN determines whether the project is being used from root # or if it is added as a dependency (through add_subdirectory for example). if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/README.md b/README.md index 1f47b32a..d779c6b9 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ 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 ++ `NDEBUG` macro should be defined for release build, or you will get poor performance For amalgamated build, run `python amalgamate.py` to generate `pocketpy.c` and `pocketpy.h` in `amalgamated/` directory. @@ -146,9 +147,6 @@ __ERROR: ## Features -Check this [Cheatsheet](https://reference.pocketpy.dev/python.html) -for a quick overview of the supported features. - | Name | Example | Supported | | --------------- | ------------------------------- | --------- | | If Else | `if..else..elif` | ✅ | @@ -237,7 +235,6 @@ Your sponsorship will help us develop pkpy continuously. [![Star History Chart](https://api.star-history.com/svg?repos=blueloveth/pocketpy&type=Date)](https://star-history.com/#blueloveth/pocketpy&Date) - ## License [MIT License](http://opensource.org/licenses/MIT) diff --git a/amalgamate.py b/amalgamate.py index 2ccfd7b2..3d4bacb1 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -8,7 +8,7 @@ from typing import List, Dict assert os.system("python prebuild.py") == 0 ROOT = 'include/pocketpy' -PUBLIC_HEADERS = ['config.h', 'export.h', 'pocketpy.h'] +PUBLIC_HEADERS = ['config.h', 'export.h', 'linalg.h', 'pocketpy.h'] COPYRIGHT = '''/* * Copyright (c) 2024 blueloveTH diff --git a/docs/bindings-cpp.md b/docs/bindings-cpp.md new file mode 100644 index 00000000..e8062fb6 --- /dev/null +++ b/docs/bindings-cpp.md @@ -0,0 +1,291 @@ +--- +icon: cpu +title: Write C++ Bindings +order: 17 +--- + +## Quick Start + +pkpy provides a [pybind11](https://pybind11.readthedocs.io/en/stable/) compatible layer which allows users to do convenient bindings. + +To begin with, use `py::scoped_interpreter guard{}` to start the interpreter before using any Python objects. +Or explicitly call `py::interpreter::initialize()` and `py::interpreter::finalize()`. + +### module + +```cpp +#include +namespace py = pybind11; + +PYBIND11_EMBEDDED_MODULE(example, m) { + m.def("add", [](int a, int b) { + return a + b; + }); + + auto math = m.def_submodule("math"); +} +``` + +### function + +```cpp +int add(int a, int b) { return a + b; } + +int add(int a, int b, int c) { return a + b + c; } + +void register_function(py::module_& m) +{ + m.def("add", py::overload_cast(&add)); + + // support function overload + m.def("add", py::overload_cast(&add)); + + // bind with default arguments + m.def("sub", [](int a, int b) { + return a - b; + }, py::arg("a") = 1, py::arg("b") = 2); + + // bind *args + m.def("add", [](py::args args) { + int sum = 0; + for (auto& arg : args) { + sum += arg.cast(); + } + return sum; + }); + + // bind **kwargs + m.def("add", [](py::kwargs kwargs) { + int sum = 0; + for (auto item : kwargs) { + sum += item.second.cast(); + } + return sum; + }); +} +``` + +### class + +```cpp +struct Point +{ + const int x; + int y; + +public: + Point() : x(0), y(0) {} + + Point(int x, int y) : x(x), y(y) {} + + Point(const Point& p) : x(p.x), y(p.y) {} + + std::string stringfy() const { + return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; + } +}; + +struct Point3D : Point +{ +private: + int z; + +public: + Point3D(int x, int y, int z) : Point(x, y), z(z) {} + + int get_z() const { return z; } + + void set_z(int z) { this->z = z; } +}; + +void bind_class(py::module_& m) +{ + py::class_(m, "Point") + .def(py::init<>()) + .def(py::init()) + .def(py::init()) + .def_readonly("x", &Point::x) + .def_readwrite("y", &Point::y) + .def("__str__", &Point::stringfy); + + // only support single inheritance + py::class_(m, "Point3D", py::dynamic_attr()) + .def(py::init()) + .def_property("z", &Point3D::get_z, &Point3D::set_z); + + // dynamic_attr will enable the dict of bound class +} +``` + +### operators + +```cpp +#include +namespace py = pybind11; + +struct Int { + int value; + + Int(int value) : value(value) {} + + Int operator+(const Int& other) const { + return Int(value + other.value); + } + + Int operator-(const Int& other) const { + return Int(value - other.value); + } + + bool operator==(const Int& other) const { + return value == other.value; + } + + bool operator!=(const Int& other) const { + return value != other.value; + } +}; + +void bind_operators(py::module_& m) +{ + py::class_(m, "Int") + .def(py::init()) + .def(py::self + py::self) + .def(py::self - py::self) + .def(py::self == py::self) + .def(py::self != py::self); + // other operators are similar +} +``` + +### py::object + +`py::object` is just simple wrapper around `PyVar`. It supports some convenient methods to interact with Python objects. + + +here are some common methods: + +```cpp +obj.attr("x"); // access attribute +obj[1]; // access item + +obj.is_none(); // same as obj is None in Python +obj.is(obj2); // same as obj is obj2 in Python + +// operators +obj + obj2; // same as obj + obj2 in Python +// ... +obj == obj2; // same as obj == obj2 in Python +// ... + +obj(...); // same as obj.__call__(...) + +py::cast(obj); // cast to Python object +obj.cast; // cast to C++ type + +py::type::of(obj); // get type of obj +py::type::of(); // get type of T, if T is registered +``` + +you can also create some builtin objects with their according wrappers: + +```cpp +py::bool_ b = {true}; +py::int_ i = {1}; +py::float_ f = {1.0}; +py::str s = {"hello"}; +py::list l = {1, 2, 3}; +py::tuple t = {1, 2, 3}; +// ... +``` + + + +## More Examples + +More examples please see the test [folder](https://github.com/pocketpy/gsoc-2024-dev/tree/main/pybind11/tests) in the GSoC repository. All tested features are supported. + +## Limits and Comparison + +This is a feature list of pybind11 for pocketpy. It lists all completed and pending features. It also lists the features that cannot be implemented in the current version of pocketpy. + +### [Function](https://pybind11.readthedocs.io/en/stable/advanced/functions.html) + +- [x] Function overloading +- [x] Return value policy +- [x] is_prepend +- [x] `*args` and `**kwargs` +- [ ] Keep-alive +- [ ] Call Guard +- [x] Default arguments +- [ ] Keyword-Only arguments +- [ ] Positional-Only arguments +- [ ] Allow/Prohibiting None arguments + +### [Class](https://pybind11.readthedocs.io/en/stable/classes.html) + +- [x] Creating bindings for a custom type +- [x] Binding lambda functions +- [x] Dynamic attributes +- [x] Inheritance and automatic downcasting +- [x] Enumerations and internal types +- [ ] Instance and static fields + +> Binding static fields may never be implemented in pocketpy because it requires a metaclass, which is a heavy and infrequently used feature. + +### [Exceptions](https://pybind11.readthedocs.io/en/stable/advanced/exceptions.html) + +Need further discussion. + +### [Smart pointers](https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html) + +- [ ] std::shared_ptr +- [ ] std::unique_ptr +- [ ] Custom smart pointers + +### [Type conversions](https://pybind11.readthedocs.io/en/stable/advanced/cast/index.html) + +- [x] Python built-in types +- [x] STL Containers +- [ ] Functional +- [ ] Chrono + +### [Python C++ interface](https://pybind11.readthedocs.io/en/stable/advanced/pycpp/object.html) + +Need further discussion. + +- [x] `object` +- [x] `none` +- [x] `type` +- [x] `bool_` +- [x] `int_` +- [x] `float_` +- [x] `str` +- [ ] `bytes` +- [ ] `bytearray` +- [x] `tuple` +- [x] `list` +- [ ] `set` +- [x] `dict` +- [ ] `slice` +- [x] `iterable` +- [x] `iterator` +- [ ] `function` +- [ ] `buffer` +- [ ] `memoryview` +- [x] `capsule` + +### [Miscellaneous](https://pybind11.readthedocs.io/en/stable/advanced/misc.html) + +- [ ] Global Interpreter Lock (GIL) +- [ ] Binding sequence data types, iterators, the slicing protocol, etc. +- [x] Convenient operators binding + +### Differences between CPython and pocketpy + +- only `add`, `sub` and `mul` have corresponding right versions in pocketpy. So if you bind `int() >> py::self`, it will has no effect in pocketpy. + +- `__new__` and `__del__` are not supported in pocketpy. + +- in-place operators, such as `+=`, `-=`, `*=`, etc., are not supported in pocketpy. + +- thre return value of `globals` is immutable in pocketpy. \ No newline at end of file diff --git a/docs/bindings.md b/docs/bindings.md index f3dfc697..4ba92689 100644 --- a/docs/bindings.md +++ b/docs/bindings.md @@ -1,6 +1,6 @@ --- icon: cpu -title: Write Bindings +title: Write C Bindings order: 18 --- @@ -16,3 +16,11 @@ typedef bool (*py_CFunction)(int argc, py_Ref argv); 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`. + +See also: ++ [`py_bind`](/c-api/functions/#py_bind) ++ [`py_bindmethod`](/c-api/functions/#py_bindmethod) ++ [`py_bindfunc`](/c-api/functions/#py_bindfunc) ++ [`py_bindproperty`](/c-api/functions/#py_bindproperty) ++ [`py_newmodule`](/c-api/functions/#py_newmodule) ++ [`py_newtype`](/c-api/functions/#py_newtype) \ No newline at end of file diff --git a/docs/features/basic.md b/docs/features/basic.md index a34a2e2b..c9af4ff7 100644 --- a/docs/features/basic.md +++ b/docs/features/basic.md @@ -4,9 +4,6 @@ title: Basic Features order: 100 --- -Check this [Cheatsheet](https://reference.pocketpy.dev/python.html) -for a quick overview of the supported features. - The following table shows the basic features of pkpy with respect to [cpython](https://github.com/python/cpython). The features marked with `YES` are supported, and the features marked with `NO` are not supported. diff --git a/docs/features/debugging.md b/docs/features/debugging.md index 526d3e25..f7383a7a 100644 --- a/docs/features/debugging.md +++ b/docs/features/debugging.md @@ -4,7 +4,7 @@ title: Debugging --- !!! -This feature is available in `v1.4.5` or higher. Set `PK_ENABLE_PROFILER` to `1` to enable this feature. +This feature is not available in `v2.0` yet. !!! You can invoke `breakpoint()` in your python code to start a PDB-like session. diff --git a/docs/features/differences.md b/docs/features/differences.md index 069b8736..746084e8 100644 --- a/docs/features/differences.md +++ b/docs/features/differences.md @@ -23,20 +23,18 @@ The easiest way to test a feature is to [try it on your browser](https://pocketp 1. Descriptor protocol `__get__` and `__set__`. However, `@property` is implemented. 2. `__slots__` in class definition. 3. `else` clause in try..except. -4. Inplace methods like `__iadd__` and `__imul__`. +4. Inplace methods like `__iadd__` and `__imul__`. 5. `__del__` in class definition. 6. Multiple inheritance. ## Different behaviors 1. positional and keyword arguments are strictly evaluated. -2. `++i` and `--j` is an increment/decrement statement, not an expression. -3. `int` does not derive from `bool`. -4. `int` is 64-bit. You can use `long` type explicitly for arbitrary sized integers. -5. `__ne__` is not required. Define `__eq__` is enough. -6. Raw string cannot have boundary quotes in it, even escaped. See [#55](https://github.com/pocketpy/pocketpy/issues/55). -7. In a starred unpacked assignment, e.g. `a, b, *c = x`, the starred variable can only be presented in the last position. `a, *b, c = x` is not supported. -8. A `Tab` is equivalent to 4 spaces. You can mix `Tab` and spaces in indentation, but it is not recommended. -9. `%`, `&`, `//`, `^` and `|` for `int` behave the same as C, not python. -10. `str.split` and `str.splitlines` will remove all empty entries. -11. `__getattr__`, `__setattr__` and `__delattr__` can only be set in cpp. +2. `int` does not derive from `bool`. +3. `int` is 64-bit. +4. Raw string cannot have boundary quotes in it, even escaped. See [#55](https://github.com/pocketpy/pocketpy/issues/55). +5. In a starred unpacked assignment, e.g. `a, b, *c = x`, the starred variable can only be presented in the last position. `a, *b, c = x` is not supported. +6. A `Tab` is equivalent to 4 spaces. You can mix `Tab` and spaces in indentation, but it is not recommended. +7. `%`, `&`, `//`, `^` and `|` for `int` behave the same as C, not python. +8. `str.split` and `str.splitlines` will remove all empty entries. + diff --git a/docs/features/precompile.md b/docs/features/precompile.md deleted file mode 100644 index 93c56ecc..00000000 --- a/docs/features/precompile.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -icon: dot -title: Precompiling ---- - -pkpy allows you to precompile python code into two special forms, which can be executed later. - -### In-memory precompilation - -You can use `vm->compile` to compile your source code into a `CodeObject_` object. -This object can be executed later by `vm->_exec`. - -```cpp -CodeObject_ code = vm->compile("print('Hello, world!')", "", EXEC_MODE); -vm->_exec(code); // Hello, world! -``` - -This `CodeObject_` object is a very non-generic form of the compiled code, -which is an in-memory form. Very efficient, but not portable. -You are not able to save it to a file or load it from a file. - - -### String precompilation - -In order to save the compiled code to a file, you need to use `vm->precompile`. -It does some basic preprocessing and outputs the result as a human-readable string. - -```cpp -// precompile the source code into a string -Str source = vm->precompile("print('Hello, world!')", "", EXEC_MODE); - -CodeObject code = vm->compile(source, "", EXEC_MODE); -vm->_exec(code); // Hello, world! -``` - -You can also use python's `compile` function to achieve the same effect. - -```python -code = compile("print('Hello, world!')", "", "exec") -exec(code) # Hello, world! -``` - -Let's take a look at the precompiled string. -```python -print(code) -``` - -```txt -pkpy:1.4.5 -0 -=1 -print -=6 -5,1,0, -6,0,,, -42,,1, -8,,,S48656c6c6f2c20776f726c6421 -43,,0, -3,,, - -``` - -Comparing with **In-memory precompilation**, -**String precompilation** drops most of the information of the original source code. -It has an encryption effect, which can protect your source code from being stolen. -This also means there is no source line information when an error occurs. - -```python -src = """ -def f(a, b): - return g(a, b) - -def g(a, b): - c = f(a, b) - d = g(a, b) - return c + d -""" - -code = compile(src, "", "exec") -exec(code) -f(1, 2) -``` - -You will get this (without source line information): -```txt -Traceback (most recent call last): - File "", line 3, in f - File "", line 6, in g - File "", line 3, in f - File "", line 6, in g - File "", line 3, in f - File "", line 6, in g - File "", line 3, in f -StackOverflowError -``` - -instead of this (with source line information): - -```txt -Traceback (most recent call last): - File "", line 2, in f - return g(a, b) - File "", line 2, in g - c = f(a, b) - File "", line 2, in f - return g(a, b) - File "", line 2, in g - c = f(a, b) - File "", line 2, in f - return g(a, b) - File "", line 2, in g - c = f(a, b) - File "", line 2, in f - return g(a, b) -StackOverflowError -``` - -!!! -String compilation has no guarantee of compatibility between different versions of pkpy. -!!! - -You can use this snippet to convert every python file in a directory into precompiled strings. - -```python -# precompile.py -import sys, os - -def precompile(filepath: str): - """Precompile a python file inplace""" - print(filepath) - with open(filepath, 'r') as f: - source = f.read() - source = compile(source, filepath, 'exec') - with open(filepath, 'w') as f: - f.write(source) - -def traverse(root: str): - """Traverse a directory and precompile every python file""" - for entry in os.listdir(root): - entrypath = os.path.join(root, entry) - if os.path.isdir(entrypath): - traverse(entrypath) - elif entrypath.endswith(".py"): - precompile(entrypath) -``` diff --git a/docs/features/ub.md b/docs/features/ub.md index 34744b0f..0b3cedc9 100644 --- a/docs/features/ub.md +++ b/docs/features/ub.md @@ -9,4 +9,4 @@ These are the undefined behaviours of pkpy. The behaviour of pkpy is undefined i 2. Call an unbound method with the wrong type of `self`. For example, `int.__add__('1', 2)`. 3. Type `T`'s `__new__` returns an object that is not an instance of `T`. 4. Call `__new__` with a type that is not a subclass of `type`. -5. `__eq__`, `__lt__` or `__contains__`, etc.. returns a value that is not a boolean. + diff --git a/docs/quick-start.md b/docs/quick-start.md index 4d039039..c6bb0daf 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -31,6 +31,7 @@ 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 ++ `NDEBUG` macro should be defined for release build, or you will get poor performance ### Get prebuilt binaries @@ -53,12 +54,6 @@ You can download an artifact there which contains the following files. │   └── x86_64 │   ├── libpocketpy.so │   └── main -├── macos -│   └── pocketpy.bundle -│   └── Contents -│   ├── Info.plist -│   └── MacOS -│   └── pocketpy └── windows └── x86_64 ├── main.exe diff --git a/include/pocketpy/common/str.h b/include/pocketpy/common/str.h index d11a2539..9f12b56c 100644 --- a/include/pocketpy/common/str.h +++ b/include/pocketpy/common/str.h @@ -4,6 +4,8 @@ #include "pocketpy/common/utils.h" #include "pocketpy/pocketpy.h" +#include + /* string */ typedef struct c11_string{ // int size | char[] | '\0' diff --git a/include/pocketpy/linalg.h b/include/pocketpy/linalg.h index b25b39fa..3b5d1893 100644 --- a/include/pocketpy/linalg.h +++ b/include/pocketpy/linalg.h @@ -22,15 +22,13 @@ typedef struct c11_vec3 { float z; } c11_vec3; -typedef struct c11_mat3x3 { - union { - struct { - float _11, _12, _13; - float _21, _22, _23; - float _31, _32, _33; - }; - - float m[3][3]; - float data[9]; +typedef union c11_mat3x3 { + struct { + float _11, _12, _13; + float _21, _22, _23; + float _31, _32, _33; }; + + float m[3][3]; + float data[9]; } c11_mat3x3; diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index 37457567..443f23db 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -2,7 +2,6 @@ #include #include -#include #include #include "pocketpy/config.h" diff --git a/include/typings/cjson.pyi b/include/typings/cjson.pyi deleted file mode 100644 index 1861d4bc..00000000 --- a/include/typings/cjson.pyi +++ /dev/null @@ -1,2 +0,0 @@ -def loads(data: str | bytes) -> object: ... -def dumps(obj: object) -> str: ... diff --git a/include/typings/linalg.pyi b/include/typings/linalg.pyi index 7d3a3221..b591b17e 100644 --- a/include/typings/linalg.pyi +++ b/include/typings/linalg.pyi @@ -14,6 +14,8 @@ class vec2: def __init__(self, x: float, y: float) -> None: ... def __repr__(self) -> str: ... + def __eq__(self, other: vec2) -> bool: ... + def __ne__(self, other: vec2) -> bool: ... def __add__(self, other: vec2) -> vec2: ... def __sub__(self, other: vec2) -> vec2: ... @@ -51,6 +53,8 @@ class vec2: class mat3x3: def __init__(self, _11, _12, _13, _21, _22, _23, _31, _32, _33) -> None: ... def __repr__(self) -> str: ... + def __eq__(self, other: mat3x3) -> bool: ... + def __ne__(self, other: mat3x3) -> bool: ... def __getitem__(self, index: tuple[int, int]) -> float: ... def __setitem__(self, index: tuple[int, int], value: float) -> None: ... @@ -101,6 +105,8 @@ class vec2i: def __init__(self, x: int, y: int) -> None: ... def __repr__(self) -> str: ... + def __eq__(self, other: vec2i) -> bool: ... + def __ne__(self, other: vec2i) -> bool: ... class vec3i: @@ -117,6 +123,8 @@ class vec3i: def __init__(self, x: int, y: int, z: int) -> None: ... def __repr__(self) -> str: ... + def __eq__(self, other: vec3i) -> bool: ... + def __ne__(self, other: vec3i) -> bool: ... class vec3: @@ -133,3 +141,5 @@ class vec3: def __init__(self, x: float, y: float, z: float) -> None: ... def __repr__(self) -> str: ... + def __eq__(self, other: vec3) -> bool: ... + def __ne__(self, other: vec3) -> bool: ... diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 69f81c83..1c53063c 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -283,12 +283,13 @@ FrameResult VM__run_top_frame(VM* self) { if(magic->type == tp_nativefunc) { if(!py_callcfunc(magic->_cfunc, 2, SECOND())) goto __ERROR; POP(); + py_assign(TOP(), py_retval()); } else { INSERT_THIRD(); // [?, a, b] *THIRD() = *magic; // [__getitem__, a, b] if(!py_vectorcall(1, 0)) goto __ERROR; + PUSH(py_retval()); } - *TOP() = self->last_retval; DISPATCH(); } TypeError("'%t' object is not subscriptable", SECOND()->type); @@ -349,7 +350,6 @@ FrameResult VM__run_top_frame(VM* self) { } else { *FOURTH() = *magic; // [__selitem__, a, b, val] if(!py_vectorcall(2, 0)) goto __ERROR; - POP(); // discard retval } DISPATCH(); } @@ -423,7 +423,6 @@ FrameResult VM__run_top_frame(VM* self) { INSERT_THIRD(); // [?, a, b] *THIRD() = *magic; // [__delitem__, a, b] if(!py_vectorcall(1, 0)) goto __ERROR; - POP(); // discard retval } DISPATCH(); } diff --git a/src/public/py_slice.c b/src/public/py_slice.c index b6ee5885..66c8110b 100644 --- a/src/public/py_slice.c +++ b/src/public/py_slice.c @@ -63,11 +63,44 @@ static bool slice_step(int argc, py_Ref argv) { return true; } +static bool slice__eq__(int argc, py_Ref argv) { + py_Ref self = py_arg(0); + py_Ref other = py_arg(1); + if(!py_istype(other, tp_slice)) { + py_newnotimplemented(py_retval()); + return true; + } + for(int i = 0; i < 3; i++) { + py_Ref lhs = py_getslot(self, i); + py_Ref rhs = py_getslot(other, i); + int res = py_equal(lhs, rhs); + if(res == -1) return false; + if(res == 0) { + py_newbool(py_retval(), false); + return true; + } + } + py_newbool(py_retval(), true); + return true; +} + +static bool slice__ne__(int argc, py_Ref argv) { + bool ok = slice__eq__(argc, argv); + if(!ok) return false; + py_Ref res = py_retval(); + if(py_isbool(res)) py_newbool(py_retval(), !py_tobool(res)); + return true; +} + py_Type pk_slice__register() { py_Type type = pk_newtype("slice", tp_object, NULL, NULL, false, true); py_bindmagic(type, __new__, slice__new__); py_bindmagic(type, __repr__, slice__repr__); + py_bindmagic(type, __eq__, slice__eq__); + py_bindmagic(type, __ne__, slice__ne__); + + py_setdict(py_tpobject(type), __hash__, py_None); py_bindproperty(type, "start", slice_start, NULL); py_bindproperty(type, "stop", slice_stop, NULL); diff --git a/tests/05_list.py b/tests/05_list.py index a6d16e45..5f6b213c 100644 --- a/tests/05_list.py +++ b/tests/05_list.py @@ -167,26 +167,6 @@ assert b == [(5, 1), (1, 2), (3,3)] # assert repr(a) == "[0, [1, 2, [...]]]" -# try: -# a.index(1, 1) -# exit(1) -# except ValueError: -# pass - -# slice extras -# class A: -# def __getitem__(self, index): -# return index - -# assert A()[1:2, 3] == (slice(1, 2, None), 3) -# assert A()[1:2, 3:4] == (slice(1, 2, None), slice(3, 4, None)) -# assert A()[1:2, 3:4, 5] == (slice(1, 2, None), slice(3, 4, None), 5) -# assert A()[:, :] == (slice(None, None, None), slice(None, None, None)) -# assert A()[::, :] == (slice(None, None, None), slice(None, None, None)) -# assert A()[::, :2] == (slice(None, None, None), slice(None, 2, None)) -# assert A()['b':'c':1, :] == (slice('b', 'c', 1), slice(None, None, None)) -# assert A()[1:2, :A()[3:4, ::-1]] == (slice(1, 2, None), slice(None, (slice(3, 4, None), slice(None, None, -1)), None)) - # unpacking builder (not supported) # a = [1, 2, 3] # b = [*a, 4, 5] diff --git a/tests/28_exception.py b/tests/28_exception.py index f5022d6b..1a9cee99 100644 --- a/tests/28_exception.py +++ b/tests/28_exception.py @@ -47,7 +47,7 @@ try: a = A() b = a[1] exit(1) -except: +except KeyError: pass try: diff --git a/tests/99_bugs.py b/tests/95_bugs.py similarity index 100% rename from tests/99_bugs.py rename to tests/95_bugs.py diff --git a/tests/99_dis.py b/tests/95_dis.py similarity index 100% rename from tests/99_dis.py rename to tests/95_dis.py diff --git a/tests/99_extras.py b/tests/99_extras.py new file mode 100644 index 00000000..f2a27c02 --- /dev/null +++ b/tests/99_extras.py @@ -0,0 +1,49 @@ +try: + a = [1, 2, 3] + a.index(999) + exit(1) +except ValueError: + pass + +# test some python magics +class A: + def __init__(self): + self.d = {} + + def __getitem__(self, index): + return self.d[index] + + def __setitem__(self, index, value): + self.d[index] = value + + def __contains__(self, index): + return index in self.d + + def __delitem__(self, index): + del self.d[index] + +a = A() +a['1'] = 3 +assert '1' in a +assert '2' not in a +assert a['1'] == 3 +del a['1'] +assert '1' not in a + +# slice extras +class A: + def __getitem__(self, index): + return index + +assert slice(1, 2, None) == slice(1, 2, None) +assert slice(1, 3, None) != slice(1, 2, None) + +assert A()[1] == 1 +assert A()[1:2, 3] == (slice(1, 2, None), 3) +assert A()[1:2, 3:4] == (slice(1, 2, None), slice(3, 4, None)) +assert A()[1:2, 3:4, 5] == (slice(1, 2, None), slice(3, 4, None), 5) +assert A()[:, :] == (slice(None, None, None), slice(None, None, None)) +assert A()[::, :] == (slice(None, None, None), slice(None, None, None)) +assert A()[::, :2] == (slice(None, None, None), slice(None, 2, None)) +assert A()['b':'c':1, :] == (slice('b', 'c', 1), slice(None, None, None)) +assert A()[1:2, :A()[3:4, ::-1]] == (slice(1, 2, None), slice(None, (slice(3, 4, None), slice(None, None, -1)), None))