Skip to content

Commit a1bfaf6

Browse files
committed
Add Vector::map/filter implementations
1 parent 00b1f4c commit a1bfaf6

File tree

5 files changed

+312
-7
lines changed

5 files changed

+312
-7
lines changed

ext/spl/spl_vector.c

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,191 @@ PHP_METHOD(Vector, indexOf)
983983
RETURN_FALSE;
984984
}
985985

986+
PHP_METHOD(Vector, filter)
987+
{
988+
zend_fcall_info fci = empty_fcall_info;
989+
zend_fcall_info_cache fci_cache = empty_fcall_info_cache;
990+
ZEND_PARSE_PARAMETERS_START(0, 1)
991+
Z_PARAM_OPTIONAL
992+
Z_PARAM_FUNC_OR_NULL(fci, fci_cache)
993+
ZEND_PARSE_PARAMETERS_END();
994+
995+
const spl_vector *intern = Z_VECTOR_P(ZEND_THIS);
996+
size_t size = 0;
997+
size_t capacity = 0;
998+
zval *entries = NULL;
999+
zval operand;
1000+
if (intern->array.size == 0) {
1001+
/* do nothing */
1002+
} else if (ZEND_FCI_INITIALIZED(fci)) {
1003+
zval retval;
1004+
1005+
fci.params = &operand;
1006+
fci.param_count = 1;
1007+
fci.retval = &retval;
1008+
1009+
for (size_t i = 0; i < intern->array.size; i++) {
1010+
ZVAL_COPY(&operand, &intern->array.entries[i]);
1011+
const int result = zend_call_function(&fci, &fci_cache);
1012+
if (UNEXPECTED(result != SUCCESS || EG(exception))) {
1013+
zval_ptr_dtor(&operand);
1014+
cleanup:
1015+
if (entries) {
1016+
for (; size > 0; size--) {
1017+
zval_ptr_dtor(&entries[size]);
1018+
}
1019+
efree(entries);
1020+
}
1021+
return;
1022+
}
1023+
const bool is_true = zend_is_true(&retval);
1024+
zval_ptr_dtor(&retval);
1025+
if (UNEXPECTED(EG(exception))) {
1026+
goto cleanup;
1027+
}
1028+
if (!is_true) {
1029+
zval_ptr_dtor(&operand);
1030+
if (UNEXPECTED(EG(exception))) {
1031+
goto cleanup;
1032+
}
1033+
continue;
1034+
}
1035+
1036+
if (size >= capacity) {
1037+
if (entries) {
1038+
capacity = size + intern->array.size - i;
1039+
entries = safe_erealloc(entries, capacity, sizeof(zval), 0);
1040+
} else {
1041+
ZEND_ASSERT(size == 0);
1042+
ZEND_ASSERT(capacity == 0);
1043+
capacity = intern->array.size > i ? intern->array.size - i : 1;
1044+
entries = safe_emalloc(capacity, sizeof(zval), 0);
1045+
}
1046+
}
1047+
ZEND_ASSERT(size < capacity);
1048+
ZVAL_COPY_VALUE(&entries[size], &operand);
1049+
size++;
1050+
}
1051+
} else {
1052+
for (size_t i = 0; i < intern->array.size; i++) {
1053+
ZVAL_COPY(&operand, &intern->array.entries[i]);
1054+
const bool is_true = zend_is_true(&operand);
1055+
if (!is_true) {
1056+
zval_ptr_dtor(&operand);
1057+
if (UNEXPECTED(EG(exception))) {
1058+
goto cleanup;
1059+
}
1060+
continue;
1061+
}
1062+
1063+
if (size >= capacity) {
1064+
if (entries) {
1065+
capacity = size + intern->array.size - i;
1066+
entries = safe_erealloc(entries, capacity, sizeof(zval), 0);
1067+
} else {
1068+
ZEND_ASSERT(size == 0);
1069+
ZEND_ASSERT(capacity == 0);
1070+
capacity = intern->array.size > i ? intern->array.size - i : 1;
1071+
entries = safe_emalloc(capacity, sizeof(zval), 0);
1072+
}
1073+
}
1074+
ZEND_ASSERT(size < capacity);
1075+
ZVAL_COPY_VALUE(&entries[size], &operand);
1076+
size++;
1077+
}
1078+
}
1079+
1080+
zend_object *new_object = spl_vector_new_ex(spl_ce_Vector, NULL, 0);
1081+
spl_vector *new_intern = spl_vector_from_object(new_object);
1082+
if (size == 0) {
1083+
ZEND_ASSERT(!entries);
1084+
spl_vector_entries_set_empty_list(&new_intern->array);
1085+
RETURN_OBJ(new_object);
1086+
}
1087+
if (capacity > size) {
1088+
/* Shrink allocated value to actual required size */
1089+
entries = erealloc(entries, size * sizeof(zval));
1090+
}
1091+
1092+
new_intern->array.entries = entries;
1093+
new_intern->array.size = size;
1094+
new_intern->array.capacity = size;
1095+
RETURN_OBJ(new_object);
1096+
}
1097+
1098+
PHP_METHOD(Vector, map)
1099+
{
1100+
zend_fcall_info fci;
1101+
zend_fcall_info_cache fci_cache;
1102+
ZEND_PARSE_PARAMETERS_START(1, 1)
1103+
Z_PARAM_FUNC(fci, fci_cache)
1104+
ZEND_PARSE_PARAMETERS_END();
1105+
1106+
const spl_vector *intern = Z_VECTOR_P(ZEND_THIS);
1107+
size_t new_capacity = intern->array.size;
1108+
1109+
if (new_capacity == 0) {
1110+
zend_object *new_object = spl_vector_new_ex(spl_ce_Vector, NULL, 0);
1111+
spl_vector *new_intern = spl_vector_from_object(new_object);
1112+
spl_vector_entries_set_empty_list(&new_intern->array);
1113+
RETURN_OBJ(new_object);
1114+
}
1115+
1116+
zval *entries = emalloc(new_capacity * sizeof(zval));
1117+
size_t size = 0;
1118+
1119+
zval operand;
1120+
fci.params = &operand;
1121+
fci.param_count = 1;
1122+
1123+
do {
1124+
if (UNEXPECTED(size >= new_capacity)) {
1125+
if (entries) {
1126+
new_capacity = size + 1;
1127+
entries = safe_erealloc(entries, new_capacity, sizeof(zval), 0);
1128+
} else {
1129+
ZEND_ASSERT(size == 0);
1130+
ZEND_ASSERT(new_capacity == 0);
1131+
new_capacity = intern->array.size;
1132+
entries = safe_emalloc(new_capacity, sizeof(zval), 0);
1133+
}
1134+
}
1135+
fci.retval = &entries[size];
1136+
ZVAL_COPY(&operand, &intern->array.entries[size]);
1137+
const int result = zend_call_function(&fci, &fci_cache);
1138+
fci.retval = &entries[size];
1139+
zval_ptr_dtor(&operand);
1140+
if (UNEXPECTED(result != SUCCESS || EG(exception))) {
1141+
cleanup:
1142+
if (entries) {
1143+
for (; size > 0; size--) {
1144+
zval_ptr_dtor(&entries[size]);
1145+
}
1146+
efree(entries);
1147+
}
1148+
return;
1149+
}
1150+
size++;
1151+
} while (size < intern->array.size);
1152+
1153+
zend_object *new_object = spl_vector_new_ex(spl_ce_Vector, NULL, 0);
1154+
spl_vector *new_intern = spl_vector_from_object(new_object);
1155+
if (size == 0) {
1156+
ZEND_ASSERT(!entries);
1157+
spl_vector_entries_set_empty_list(&new_intern->array);
1158+
RETURN_OBJ(new_object);
1159+
}
1160+
if (UNEXPECTED(new_capacity > size)) {
1161+
/* Shrink allocated value to actual required size */
1162+
entries = erealloc(entries, size * sizeof(zval));
1163+
}
1164+
1165+
new_intern->array.entries = entries;
1166+
new_intern->array.size = size;
1167+
new_intern->array.capacity = size;
1168+
RETURN_OBJ(new_object);
1169+
}
1170+
9861171
PHP_METHOD(Vector, contains)
9871172
{
9881173
zval *value;
@@ -1213,7 +1398,6 @@ PHP_MINIT_FUNCTION(spl_vector)
12131398
spl_handler_Vector.count_elements = spl_vector_count_elements;
12141399
spl_handler_Vector.get_properties = spl_vector_get_properties;
12151400
spl_handler_Vector.get_gc = spl_vector_get_gc;
1216-
spl_handler_Vector.dtor_obj = zend_objects_destroy_object;
12171401
spl_handler_Vector.free_obj = spl_vector_free_storage;
12181402

12191403
spl_handler_Vector.read_dimension = spl_vector_read_dimension;

ext/spl/spl_vector.stub.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public function __construct(iterable $iterator = [], bool $preserveKeys = true)
1515
public function getIterator(): InternalIterator {}
1616
public function count(): int {}
1717
public function capacity(): int {}
18+
public function shrinkToFit(): void {}
1819
public function clear(): void {}
1920
public function setSize(int $size): void {}
2021

@@ -39,7 +40,17 @@ public function offsetUnset(mixed $offset): void {}
3940
public function indexOf(mixed $value): int|false {}
4041
public function contains(mixed $value): bool {}
4142

42-
public function shrinkToFit(): void {}
43+
public function map(callable $callback): Vector {}
44+
/**
45+
* Returns the subset of elements of the Vector satisfying the predicate.
46+
*
47+
* If the value returned by the callback is truthy
48+
* (e.g. true, non-zero number, non-empty array, truthy object, etc.),
49+
* this is treated as satisfying the predicate.
50+
*
51+
* (at)param null|callable(mixed):mixed $callback
52+
*/
53+
public function filter(?callable $callback = null): Vector {}
4354

4455
public function jsonSerialize(): array {}
4556
}

ext/spl/spl_vector_arginfo.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: b283cbbdbd13b6231744dff4b39dd648a889082e */
2+
* Stub hash: 67a8a475004563eb10b9c117d32b864c94c13c38 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Vector___construct, 0, 0, 0)
55
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, iterator, IS_ITERABLE, 0, "[]")
@@ -14,9 +14,11 @@ ZEND_END_ARG_INFO()
1414

1515
#define arginfo_class_Vector_capacity arginfo_class_Vector_count
1616

17-
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_clear, 0, 0, IS_VOID, 0)
17+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_shrinkToFit, 0, 0, IS_VOID, 0)
1818
ZEND_END_ARG_INFO()
1919

20+
#define arginfo_class_Vector_clear arginfo_class_Vector_shrinkToFit
21+
2022
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_setSize, 0, 1, IS_VOID, 0)
2123
ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0)
2224
ZEND_END_ARG_INFO()
@@ -75,7 +77,13 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Vector_contains, 0, 1, _IS
7577
ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0)
7678
ZEND_END_ARG_INFO()
7779

78-
#define arginfo_class_Vector_shrinkToFit arginfo_class_Vector_clear
80+
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Vector_map, 0, 1, Vector, 0)
81+
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
82+
ZEND_END_ARG_INFO()
83+
84+
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Vector_filter, 0, 0, Vector, 0)
85+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, callback, IS_CALLABLE, 1, "null")
86+
ZEND_END_ARG_INFO()
7987

8088
#define arginfo_class_Vector_jsonSerialize arginfo_class_Vector___serialize
8189

@@ -84,6 +92,7 @@ ZEND_METHOD(Vector, __construct);
8492
ZEND_METHOD(Vector, getIterator);
8593
ZEND_METHOD(Vector, count);
8694
ZEND_METHOD(Vector, capacity);
95+
ZEND_METHOD(Vector, shrinkToFit);
8796
ZEND_METHOD(Vector, clear);
8897
ZEND_METHOD(Vector, setSize);
8998
ZEND_METHOD(Vector, __serialize);
@@ -100,7 +109,8 @@ ZEND_METHOD(Vector, offsetSet);
100109
ZEND_METHOD(Vector, offsetUnset);
101110
ZEND_METHOD(Vector, indexOf);
102111
ZEND_METHOD(Vector, contains);
103-
ZEND_METHOD(Vector, shrinkToFit);
112+
ZEND_METHOD(Vector, map);
113+
ZEND_METHOD(Vector, filter);
104114
ZEND_METHOD(Vector, jsonSerialize);
105115

106116

@@ -109,6 +119,7 @@ static const zend_function_entry class_Vector_methods[] = {
109119
ZEND_ME(Vector, getIterator, arginfo_class_Vector_getIterator, ZEND_ACC_PUBLIC)
110120
ZEND_ME(Vector, count, arginfo_class_Vector_count, ZEND_ACC_PUBLIC)
111121
ZEND_ME(Vector, capacity, arginfo_class_Vector_capacity, ZEND_ACC_PUBLIC)
122+
ZEND_ME(Vector, shrinkToFit, arginfo_class_Vector_shrinkToFit, ZEND_ACC_PUBLIC)
112123
ZEND_ME(Vector, clear, arginfo_class_Vector_clear, ZEND_ACC_PUBLIC)
113124
ZEND_ME(Vector, setSize, arginfo_class_Vector_setSize, ZEND_ACC_PUBLIC)
114125
ZEND_ME(Vector, __serialize, arginfo_class_Vector___serialize, ZEND_ACC_PUBLIC)
@@ -125,7 +136,8 @@ static const zend_function_entry class_Vector_methods[] = {
125136
ZEND_ME(Vector, offsetUnset, arginfo_class_Vector_offsetUnset, ZEND_ACC_PUBLIC)
126137
ZEND_ME(Vector, indexOf, arginfo_class_Vector_indexOf, ZEND_ACC_PUBLIC)
127138
ZEND_ME(Vector, contains, arginfo_class_Vector_contains, ZEND_ACC_PUBLIC)
128-
ZEND_ME(Vector, shrinkToFit, arginfo_class_Vector_shrinkToFit, ZEND_ACC_PUBLIC)
139+
ZEND_ME(Vector, map, arginfo_class_Vector_map, ZEND_ACC_PUBLIC)
140+
ZEND_ME(Vector, filter, arginfo_class_Vector_filter, ZEND_ACC_PUBLIC)
129141
ZEND_ME(Vector, jsonSerialize, arginfo_class_Vector_jsonSerialize, ZEND_ACC_PUBLIC)
130142
ZEND_FE_END
131143
};

ext/spl/tests/Vector/filter.phpt

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
--TEST--
2+
Vector filter()
3+
--FILE--
4+
<?php
5+
function dump_vector(Vector $v) {
6+
printf("count=%d capacity=%d\n", $v->count(), $v->capacity());
7+
foreach ($v as $i => $value) {
8+
printf("Value #%d: %s\n", $i, var_export($value, true));
9+
}
10+
}
11+
dump_vector((new Vector([false]))->filter());
12+
dump_vector((new Vector([true]))->filter());
13+
dump_vector((new Vector([strtoupper('test'), true, false, new stdClass()]))->filter());
14+
echo "Test functions\n";
15+
$negate = fn($x) => !$x;
16+
dump_vector((new Vector([]))->filter($negate));
17+
dump_vector((new Vector([true]))->filter($negate));
18+
dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->filter($negate));
19+
$identity = fn($x) => $x;
20+
dump_vector((new Vector([]))->filter($identity));
21+
dump_vector((new Vector([true]))->filter($identity));
22+
dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->filter($identity));
23+
try {
24+
(new Vector([strtoupper('first'), 'not reached']))->filter(function ($parameter) {
25+
var_dump($parameter);
26+
throw new RuntimeException("test");
27+
});
28+
echo "Should throw\n";
29+
} catch (Exception $e) {
30+
printf("Caught %s: %s\n", get_class($e), $e->getMessage());
31+
}
32+
?>
33+
--EXPECT--
34+
count=0 capacity=0
35+
count=1 capacity=1
36+
Value #0: true
37+
count=3 capacity=3
38+
Value #0: 'TEST'
39+
Value #1: true
40+
Value #2: (object) array(
41+
)
42+
Test functions
43+
count=0 capacity=0
44+
count=0 capacity=0
45+
count=2 capacity=2
46+
Value #0: false
47+
Value #1: array (
48+
)
49+
count=0 capacity=0
50+
count=1 capacity=1
51+
Value #0: true
52+
count=3 capacity=3
53+
Value #0: 'TEST'
54+
Value #1: true
55+
Value #2: (object) array(
56+
)
57+
string(5) "FIRST"
58+
Caught RuntimeException: test

ext/spl/tests/Vector/map.phpt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
Vector map()
3+
--FILE--
4+
<?php
5+
function dump_vector(Vector $v) {
6+
printf("count=%d capacity=%d\n", $v->count(), $v->capacity());
7+
foreach ($v as $i => $value) {
8+
printf("Value #%d: %s\n", $i, json_encode($value));
9+
}
10+
}
11+
$create_array = fn($x) => [$x];
12+
dump_vector((new Vector())->map($create_array));
13+
dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->map($create_array));
14+
$identity = fn($x) => $x;
15+
dump_vector((new Vector())->map($identity));
16+
dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->map($identity));
17+
$false = fn() => false;
18+
dump_vector((new Vector([strtoupper('test'), true, false, new stdClass(), []]))->map($false));
19+
?>
20+
--EXPECT--
21+
count=0 capacity=0
22+
count=5 capacity=5
23+
Value #0: ["TEST"]
24+
Value #1: [true]
25+
Value #2: [false]
26+
Value #3: [{}]
27+
Value #4: [[]]
28+
count=0 capacity=0
29+
count=5 capacity=5
30+
Value #0: "TEST"
31+
Value #1: true
32+
Value #2: false
33+
Value #3: {}
34+
Value #4: []
35+
count=5 capacity=5
36+
Value #0: false
37+
Value #1: false
38+
Value #2: false
39+
Value #3: false
40+
Value #4: false

0 commit comments

Comments
 (0)