mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-21 12:00:18 +00:00
add array2d
module
This commit is contained in:
parent
458f3c84a6
commit
dcb784a7a8
@ -9,7 +9,7 @@ pipeline = [
|
||||
["config.h", "export.h", "common.h", "memory.h", "vector.h", "str.h", "tuplelist.h", "namedict.h", "error.h"],
|
||||
["obj.h", "dict.h", "codeobject.h", "frame.h"],
|
||||
["gc.h", "vm.h", "ceval.h", "lexer.h", "expr.h", "compiler.h", "repl.h"],
|
||||
["_generated.h", "cffi.h", "bindings.h", "iter.h", "base64.h", "csv.h", "collections.h", "dataclasses.h", "random.h", "linalg.h", "easing.h", "io.h", "modules.h"],
|
||||
["_generated.h", "cffi.h", "bindings.h", "iter.h", "base64.h", "csv.h", "collections.h", "array2d.h", "dataclasses.h", "random.h", "linalg.h", "easing.h", "io.h", "modules.h"],
|
||||
["pocketpy.h", "pocketpy_c.h"]
|
||||
]
|
||||
|
||||
|
9
include/pocketpy/array2d.h
Normal file
9
include/pocketpy/array2d.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "bindings.h"
|
||||
|
||||
namespace pkpy {
|
||||
|
||||
void add_module_array2d(VM* vm);
|
||||
|
||||
} // namespace pkpy
|
@ -15,4 +15,5 @@
|
||||
#include "collections.h"
|
||||
#include "csv.h"
|
||||
#include "dataclasses.h"
|
||||
#include "array2d.h"
|
||||
#include "modules.h"
|
||||
|
@ -325,6 +325,7 @@ public:
|
||||
|
||||
int normalized_index(int index, int size);
|
||||
PyObject* py_next(PyObject* obj);
|
||||
bool py_callable(PyObject* obj);
|
||||
|
||||
/***** Error Reporter *****/
|
||||
void _raise(bool re_raise=false);
|
||||
|
91
include/typings/array2d.pyi
Normal file
91
include/typings/array2d.pyi
Normal file
@ -0,0 +1,91 @@
|
||||
from typing import Callable, Any, Generic, TypeVar
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class array2d(Generic[T]):
|
||||
data: list[T] # not available in native module
|
||||
|
||||
def __init__(self, n_cols: int, n_rows: int, default=None):
|
||||
self.n_cols = n_cols
|
||||
self.n_rows = n_rows
|
||||
if callable(default):
|
||||
self.data = [default() for _ in range(n_cols * n_rows)]
|
||||
else:
|
||||
self.data = [default] * n_cols * n_rows
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return self.n_cols
|
||||
|
||||
@property
|
||||
def height(self) -> int:
|
||||
return self.n_rows
|
||||
|
||||
@property
|
||||
def numel(self) -> int:
|
||||
return self.n_cols * self.n_rows
|
||||
|
||||
def is_valid(self, col: int, row: int) -> bool:
|
||||
return 0 <= col < self.n_cols and 0 <= row < self.n_rows
|
||||
|
||||
def get(self, col: int, row: int, default=None):
|
||||
if not self.is_valid(col, row):
|
||||
return default
|
||||
return self.data[row * self.n_cols + col]
|
||||
|
||||
def __getitem__(self, index: tuple[int, int]):
|
||||
col, row = index
|
||||
if not self.is_valid(col, row):
|
||||
raise IndexError(f'({col}, {row}) is not a valid index for {self!r}')
|
||||
return self.data[row * self.n_cols + col]
|
||||
|
||||
def __setitem__(self, index: tuple[int, int], value: T):
|
||||
col, row = index
|
||||
if not self.is_valid(col, row):
|
||||
raise IndexError(f'({col}, {row}) is not a valid index for {self!r}')
|
||||
self.data[row * self.n_cols + col] = value
|
||||
|
||||
def __iter__(self) -> list[list['T']]:
|
||||
for row in range(self.n_rows):
|
||||
yield [self[col, row] for col in range(self.n_cols)]
|
||||
|
||||
def __len__(self):
|
||||
return self.n_rows
|
||||
|
||||
def __eq__(self, other: 'array2d') -> bool:
|
||||
if not isinstance(other, array2d):
|
||||
return NotImplemented
|
||||
for i in range(self.numel):
|
||||
if self.data[i] != other.data[i]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other: 'array2d') -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return f'array2d({self.n_cols}, {self.n_rows})'
|
||||
|
||||
def map(self, f: Callable[[T], Any]) -> 'array2d':
|
||||
new_a: array2d = array2d(self.n_cols, self.n_rows)
|
||||
for i in range(self.n_cols * self.n_rows):
|
||||
new_a.data[i] = f(self.data[i])
|
||||
return new_a
|
||||
|
||||
def copy(self) -> 'array2d[T]':
|
||||
new_a: array2d[T] = array2d(self.n_cols, self.n_rows)
|
||||
new_a.data = self.data.copy()
|
||||
return new_a
|
||||
|
||||
def fill_(self, value: T) -> None:
|
||||
for i in range(self.n_cols * self.n_rows):
|
||||
self.data[i] = value
|
||||
|
||||
def apply_(self, f: Callable[[T], T]) -> None:
|
||||
for i in range(self.n_cols * self.n_rows):
|
||||
self.data[i] = f(self.data[i])
|
||||
|
||||
def copy_(self, other: 'array2d[T]') -> None:
|
||||
self.n_cols = other.n_cols
|
||||
self.n_rows = other.n_rows
|
||||
self.data = other.data.copy()
|
201
src/array2d.cpp
Normal file
201
src/array2d.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
#include "pocketpy/array2d.h"
|
||||
|
||||
namespace pkpy{
|
||||
|
||||
struct Array2d{
|
||||
PK_ALWAYS_PASS_BY_POINTER(Array2d)
|
||||
PY_CLASS(Array2d, array2d, array2d)
|
||||
|
||||
PyObject** data;
|
||||
int n_cols;
|
||||
int n_rows;
|
||||
int numel;
|
||||
|
||||
Array2d(){
|
||||
data = nullptr;
|
||||
n_cols = 0;
|
||||
n_rows = 0;
|
||||
numel = 0;
|
||||
}
|
||||
|
||||
Array2d* _() { return this; }
|
||||
|
||||
void init(int n_cols, int n_rows){
|
||||
this->n_cols = n_cols;
|
||||
this->n_rows = n_rows;
|
||||
this->numel = n_cols * n_rows;
|
||||
this->data = new PyObject*[numel];
|
||||
}
|
||||
|
||||
bool is_valid(int col, int row) const{
|
||||
return 0 <= col && col < n_cols && 0 <= row && row < n_rows;
|
||||
}
|
||||
|
||||
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
||||
vm->bind(type, "__new__(cls, *args, **kwargs)", [](VM* vm, ArgsView args){
|
||||
Type cls = PK_OBJ_GET(Type, args[0]);
|
||||
return vm->heap.gcnew<Array2d>(cls);
|
||||
});
|
||||
|
||||
vm->bind(type, "__init__(self, n_cols: int, n_rows: int, default=None)", [](VM* vm, ArgsView args){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, args[0]);
|
||||
int n_cols = CAST(int, args[1]);
|
||||
int n_rows = CAST(int, args[2]);
|
||||
if(n_cols <= 0 || n_rows <= 0){
|
||||
vm->ValueError("n_cols and n_rows must be positive integers");
|
||||
}
|
||||
self.init(n_cols, n_rows);
|
||||
if(vm->py_callable(args[3])){
|
||||
for(int i = 0; i < self.numel; i++) self.data[i] = vm->call(args[3]);
|
||||
}else{
|
||||
for(int i = 0; i < self.numel; i++) self.data[i] = args[3];
|
||||
}
|
||||
return vm->None;
|
||||
});
|
||||
|
||||
PY_READONLY_FIELD(Array2d, "n_cols", _, n_cols);
|
||||
PY_READONLY_FIELD(Array2d, "n_rows", _, n_rows);
|
||||
PY_READONLY_FIELD(Array2d, "width", _, n_cols);
|
||||
PY_READONLY_FIELD(Array2d, "height", _, n_rows);
|
||||
PY_READONLY_FIELD(Array2d, "numel", _, numel);
|
||||
|
||||
vm->bind(type, "is_valid(self, col: int, row: int)", [](VM* vm, ArgsView args){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, args[0]);
|
||||
int col = CAST(int, args[1]);
|
||||
int row = CAST(int, args[2]);
|
||||
return VAR(self.is_valid(col, row));
|
||||
});
|
||||
|
||||
vm->bind(type, "get(self, col: int, row: int, default=None)", [](VM* vm, ArgsView args){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, args[0]);
|
||||
int col = CAST(int, args[1]);
|
||||
int row = CAST(int, args[2]);
|
||||
if(!self.is_valid(col, row)) return args[3];
|
||||
return self.data[row * self.n_cols + col];
|
||||
});
|
||||
|
||||
vm->bind__getitem__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0, PyObject* _1){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, _0);
|
||||
const Tuple& xy = CAST(Tuple&, _1);
|
||||
int col = CAST(int, xy[0]);
|
||||
int row = CAST(int, xy[1]);
|
||||
if(!self.is_valid(col, row)){
|
||||
vm->IndexError(_S('(', col, ", ", row, ')', " is not a valid index for array2d(", self.n_cols, ", ", self.n_rows, ')'));
|
||||
}
|
||||
return self.data[row * self.n_cols + col];
|
||||
});
|
||||
|
||||
vm->bind__setitem__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0, PyObject* _1, PyObject* _2){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, _0);
|
||||
const Tuple& xy = CAST(Tuple&, _1);
|
||||
int col = CAST(int, xy[0]);
|
||||
int row = CAST(int, xy[1]);
|
||||
if(!self.is_valid(col, row)){
|
||||
vm->IndexError(_S('(', col, ", ", row, ')', " is not a valid index for array2d(", self.n_cols, ", ", self.n_rows, ')'));
|
||||
}
|
||||
self.data[row * self.n_cols + col] = _2;
|
||||
});
|
||||
|
||||
vm->bind__iter__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, _0);
|
||||
List t(self.n_rows);
|
||||
List row(self.n_cols);
|
||||
for(int i = 0; i < self.n_rows; i++){
|
||||
for(int j = 0; j < self.n_cols; j++){
|
||||
row[j] = self.data[i * self.n_cols + j];
|
||||
}
|
||||
t[i] = VAR(row); // copy
|
||||
}
|
||||
return vm->py_iter(VAR(std::move(t)));
|
||||
});
|
||||
|
||||
vm->bind__len__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, _0);
|
||||
return (i64)self.n_rows;
|
||||
});
|
||||
|
||||
vm->bind__repr__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, _0);
|
||||
return VAR(_S("array2d(", self.n_cols, ", ", self.n_rows, ')'));
|
||||
});
|
||||
|
||||
vm->bind(type, "map(self, f)", [](VM* vm, ArgsView args){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, args[0]);
|
||||
PyObject* f = args[1];
|
||||
PyObject* new_array_obj = vm->heap.gcnew<Array2d>(Array2d::_type(vm));
|
||||
Array2d& new_array = PK_OBJ_GET(Array2d, new_array_obj);
|
||||
new_array.init(self.n_cols, self.n_rows);
|
||||
for(int i = 0; i < new_array.numel; i++){
|
||||
new_array.data[i] = vm->call(f, self.data[i]);
|
||||
}
|
||||
return new_array_obj;
|
||||
});
|
||||
|
||||
vm->bind(type, "copy(self)", [](VM* vm, ArgsView args){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, args[0]);
|
||||
PyObject* new_array_obj = vm->heap.gcnew<Array2d>(Array2d::_type(vm));
|
||||
Array2d& new_array = PK_OBJ_GET(Array2d, new_array_obj);
|
||||
new_array.init(self.n_cols, self.n_rows);
|
||||
for(int i = 0; i < new_array.numel; i++){
|
||||
new_array.data[i] = self.data[i];
|
||||
}
|
||||
return new_array_obj;
|
||||
});
|
||||
|
||||
vm->bind(type, "fill_(self, value)", [](VM* vm, ArgsView args){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, args[0]);
|
||||
for(int i = 0; i < self.numel; i++){
|
||||
self.data[i] = args[1];
|
||||
}
|
||||
return vm->None;
|
||||
});
|
||||
|
||||
vm->bind(type, "apply_(self, f)", [](VM* vm, ArgsView args){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, args[0]);
|
||||
PyObject* f = args[1];
|
||||
for(int i = 0; i < self.numel; i++){
|
||||
self.data[i] = vm->call(f, self.data[i]);
|
||||
}
|
||||
return vm->None;
|
||||
});
|
||||
|
||||
vm->bind(type, "copy_(self, other)", [](VM* vm, ArgsView args){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, args[0]);
|
||||
Array2d& other = CAST(Array2d&, args[1]);
|
||||
delete self.data;
|
||||
self.init(other.n_cols, other.n_rows);
|
||||
for(int i = 0; i < self.numel; i++){
|
||||
self.data[i] = other.data[i];
|
||||
}
|
||||
return vm->None;
|
||||
});
|
||||
|
||||
vm->bind__eq__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0, PyObject* _1){
|
||||
Array2d& self = PK_OBJ_GET(Array2d, _0);
|
||||
if(!is_non_tagged_type(_1, Array2d::_type(vm))) return vm->NotImplemented;
|
||||
Array2d& other = PK_OBJ_GET(Array2d, _1);
|
||||
if(self.n_cols != other.n_cols || self.n_rows != other.n_rows) return vm->False;
|
||||
for(int i = 0; i < self.numel; i++){
|
||||
if(vm->py_ne(self.data[i], other.data[i])) return vm->False;
|
||||
}
|
||||
return vm->True;
|
||||
});
|
||||
}
|
||||
|
||||
void _gc_mark() const{
|
||||
for(int i = 0; i < numel; i++) PK_OBJ_MARK(data[i]);
|
||||
}
|
||||
|
||||
~Array2d(){
|
||||
delete[] data;
|
||||
}
|
||||
};
|
||||
|
||||
void add_module_array2d(VM* vm){
|
||||
PyObject* mod = vm->new_module("array2d");
|
||||
|
||||
Array2d::register_class(vm, mod);
|
||||
}
|
||||
|
||||
|
||||
} // namespace pkpy
|
@ -142,15 +142,7 @@ void init_builtins(VM* _vm) {
|
||||
});
|
||||
|
||||
_vm->bind_func<1>(_vm->builtins, "callable", [](VM* vm, ArgsView args) {
|
||||
Type cls = vm->_tp(args[0]);
|
||||
switch(cls.index){
|
||||
case VM::tp_function.index: return vm->True;
|
||||
case VM::tp_native_func.index: return vm->True;
|
||||
case VM::tp_bound_method.index: return vm->True;
|
||||
case VM::tp_type.index: return vm->True;
|
||||
}
|
||||
bool ok = vm->find_name_in_mro(cls, __call__) != nullptr;
|
||||
return VAR(ok);
|
||||
return VAR(vm->py_callable(args[0]));
|
||||
});
|
||||
|
||||
_vm->bind_func<1>(_vm->builtins, "__import__", [](VM* vm, ArgsView args) {
|
||||
@ -1509,8 +1501,6 @@ void VM::post_init(){
|
||||
add_module_random(this);
|
||||
add_module_base64(this);
|
||||
add_module_operator(this);
|
||||
add_module_csv(this);
|
||||
add_module_dataclasses(this);
|
||||
|
||||
for(const char* name: {"this", "functools", "heapq", "bisect", "pickle", "_long", "colorsys", "typing", "datetime", "cmath"}){
|
||||
_lazy_modules[name] = kPythonLibs[name];
|
||||
@ -1533,9 +1523,12 @@ void VM::post_init(){
|
||||
_import_handler = _default_import_handler;
|
||||
}
|
||||
|
||||
add_module_csv(this);
|
||||
add_module_dataclasses(this);
|
||||
add_module_linalg(this);
|
||||
add_module_easing(this);
|
||||
add_module_collections(this);
|
||||
add_module_array2d(this);
|
||||
|
||||
#ifdef PK_USE_CJSON
|
||||
add_module_cjson(this);
|
||||
|
12
src/vm.cpp
12
src/vm.cpp
@ -243,7 +243,6 @@ namespace pkpy{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int VM::normalized_index(int index, int size){
|
||||
if(index < 0) index += size;
|
||||
if(index < 0 || index >= size){
|
||||
@ -258,6 +257,17 @@ namespace pkpy{
|
||||
return call_method(obj, __next__);
|
||||
}
|
||||
|
||||
bool VM::py_callable(PyObject* obj){
|
||||
Type cls = vm->_tp(obj);
|
||||
switch(cls.index){
|
||||
case VM::tp_function.index: return vm->True;
|
||||
case VM::tp_native_func.index: return vm->True;
|
||||
case VM::tp_bound_method.index: return vm->True;
|
||||
case VM::tp_type.index: return vm->True;
|
||||
}
|
||||
return vm->find_name_in_mro(cls, __call__) != nullptr;
|
||||
}
|
||||
|
||||
PyObject* VM::py_import(Str path, bool throw_err){
|
||||
if(path.empty()) vm->ValueError("empty module name");
|
||||
static auto f_join = [](const std::vector<std::string_view>& cpnts){
|
||||
|
101
tests/80_array2d.py
Normal file
101
tests/80_array2d.py
Normal file
@ -0,0 +1,101 @@
|
||||
from array2d import array2d
|
||||
|
||||
# test error args for __init__
|
||||
try:
|
||||
a = array2d(0, 0)
|
||||
exit(0)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# test callable constructor
|
||||
a = array2d(2, 4, default=lambda: 0)
|
||||
|
||||
assert a.width == a.n_cols == 2
|
||||
assert a.height == a.n_rows == 4
|
||||
assert a.numel == 8
|
||||
|
||||
# test is_valid
|
||||
assert a.is_valid(0, 0)
|
||||
assert a.is_valid(1, 3)
|
||||
assert not a.is_valid(2, 0)
|
||||
assert not a.is_valid(0, 4)
|
||||
assert not a.is_valid(-1, 0)
|
||||
assert not a.is_valid(0, -1)
|
||||
|
||||
# test get
|
||||
assert a.get(0, 0) == 0
|
||||
assert a.get(1, 3) == 0
|
||||
assert a.get(2, 0) is None
|
||||
assert a.get(0, 4, default='S') == 'S'
|
||||
|
||||
# test __getitem__
|
||||
assert a[0, 0] == 0
|
||||
assert a[1, 3] == 0
|
||||
try:
|
||||
a[2, 0]
|
||||
exit(1)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
# test __setitem__
|
||||
a[0, 0] = 5
|
||||
assert a[0, 0] == 5
|
||||
a[1, 3] = 6
|
||||
assert a[1, 3] == 6
|
||||
try:
|
||||
a[0, -1] = 7
|
||||
exit(1)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
# test __iter__
|
||||
a_list = [[5, 0], [0, 0], [0, 0], [0, 6]]
|
||||
assert a_list == list(a)
|
||||
|
||||
# test __len__
|
||||
assert len(a) == 4
|
||||
|
||||
# test __eq__
|
||||
x = array2d(2, 4, default=0)
|
||||
b = array2d(2, 4, default=0)
|
||||
assert x == b
|
||||
|
||||
b[0, 0] = 1
|
||||
assert x != b
|
||||
|
||||
# test __repr__
|
||||
assert repr(a) == f'array2d(2, 4)'
|
||||
|
||||
# test map
|
||||
c = a.map(lambda x: x + 1)
|
||||
assert list(c) == [[6, 1], [1, 1], [1, 1], [1, 7]]
|
||||
assert list(a) == [[5, 0], [0, 0], [0, 0], [0, 6]]
|
||||
assert c.width == c.n_cols == 2
|
||||
assert c.height == c.n_rows == 4
|
||||
assert c.numel == 8
|
||||
|
||||
# test copy
|
||||
d = c.copy()
|
||||
assert d == c and d is not c
|
||||
|
||||
# test fill_
|
||||
d.fill_(-3)
|
||||
assert d == array2d(2, 4, default=-3)
|
||||
|
||||
# test apply_
|
||||
d.apply_(lambda x: x + 3)
|
||||
assert d == array2d(2, 4, default=0)
|
||||
|
||||
# test copy_
|
||||
a.copy_(d)
|
||||
assert a == d and a is not d
|
||||
|
||||
# test subclass array2d
|
||||
class A(array2d):
|
||||
def __init__(self):
|
||||
super().__init__(2, 4, default=0)
|
||||
|
||||
assert A().width == 2
|
||||
assert A().height == 4
|
||||
assert A().numel == 8
|
||||
assert A().get(0, 0, default=2) == 0
|
Loading…
x
Reference in New Issue
Block a user