add array2d module

This commit is contained in:
blueloveTH 2024-02-06 23:25:40 +08:00
parent 458f3c84a6
commit dcb784a7a8
9 changed files with 420 additions and 13 deletions

View File

@ -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"]
]

View File

@ -0,0 +1,9 @@
#pragma once
#include "bindings.h"
namespace pkpy {
void add_module_array2d(VM* vm);
} // namespace pkpy

View File

@ -15,4 +15,5 @@
#include "collections.h"
#include "csv.h"
#include "dataclasses.h"
#include "array2d.h"
#include "modules.h"

View File

@ -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);

View 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
View 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

View File

@ -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);

View File

@ -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
View 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