Skip to content

Commit 0cfa5b1

Browse files
committed
Prototype iterator_zip
1 parent e4ad271 commit 0cfa5b1

File tree

6 files changed

+304
-2
lines changed

6 files changed

+304
-2
lines changed

Zend/zend_interfaces.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,12 +509,17 @@ ZEND_API zend_result zend_create_internal_iterator_zval(zval *return_value, zval
509509
return FAILURE;
510510
}
511511

512+
zend_create_internal_iterator_iter(return_value, iter);
513+
return SUCCESS;
514+
}
515+
516+
ZEND_API void zend_create_internal_iterator_iter(zval *return_value, zend_object_iterator *iter)
517+
{
512518
zend_internal_iterator *intern =
513519
(zend_internal_iterator *) zend_internal_iterator_create(zend_ce_internal_iterator);
514520
intern->iter = iter;
515521
intern->iter->index = 0;
516522
ZVAL_OBJ(return_value, &intern->std);
517-
return SUCCESS;
518523
}
519524

520525
static void zend_internal_iterator_free(zend_object *obj) {

Zend/zend_interfaces.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ ZEND_API int zend_user_serialize(zval *object, unsigned char **buffer, size_t *b
7575
ZEND_API int zend_user_unserialize(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);
7676

7777
ZEND_API zend_result zend_create_internal_iterator_zval(zval *return_value, zval *obj);
78+
ZEND_API void zend_create_internal_iterator_iter(zval *return_value, zend_object_iterator *iter);
7879

7980
END_EXTERN_C()
8081

ext/spl/php_spl.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,5 @@ function iterator_apply(Traversable $iterator, callable $callback, ?array $args
5151
function iterator_count(iterable $iterator): int {}
5252

5353
function iterator_to_array(iterable $iterator, bool $preserve_keys = true): array {}
54+
55+
function iterator_zip(iterable... $iterators): InternalIterator {}

ext/spl/php_spl_arginfo.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/spl/spl_iterators.c

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3026,6 +3026,220 @@ PHP_FUNCTION(iterator_to_array)
30263026
spl_iterator_apply(obj, use_keys ? spl_iterator_to_array_apply : spl_iterator_to_values_apply, (void*)return_value);
30273027
} /* }}} */
30283028

3029+
typedef struct {
3030+
HashPosition hash_position_or_tag; /* uses the fact that index UINT32_MAX is not possible for arrays */
3031+
union {
3032+
zend_array *array;
3033+
zend_object_iterator *obj_iter;
3034+
};
3035+
} spl_zip_iterator_entry;
3036+
3037+
typedef struct {
3038+
zend_object_iterator intern;
3039+
spl_zip_iterator_entry *iterators;
3040+
uint32_t iterator_count;
3041+
} spl_zip_iterator;
3042+
3043+
static zend_always_inline bool spl_zip_iterator_is_obj_entry(const spl_zip_iterator_entry *entry)
3044+
{
3045+
return entry->hash_position_or_tag == UINT32_MAX;
3046+
}
3047+
3048+
static void spl_iterator_zip_dtor(zend_object_iterator *iter)
3049+
{
3050+
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
3051+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3052+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3053+
if (spl_zip_iterator_is_obj_entry(current)) {
3054+
zend_iterator_dtor(current->obj_iter);
3055+
} else {
3056+
zend_array_release(current->array);
3057+
}
3058+
}
3059+
zval_ptr_dtor(&iter->data);
3060+
efree(zip_iterator->iterators);
3061+
}
3062+
3063+
static zend_result spl_iterator_zip_valid(zend_object_iterator *iter)
3064+
{
3065+
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
3066+
3067+
uint32_t i = 0;
3068+
for (; i < zip_iterator->iterator_count; i++) {
3069+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3070+
if (spl_zip_iterator_is_obj_entry(current)) {
3071+
if (current->obj_iter->funcs->valid(current->obj_iter) != SUCCESS) {
3072+
return FAILURE;
3073+
}
3074+
} else {
3075+
current->hash_position_or_tag = zend_hash_get_current_pos_ex(current->array, current->hash_position_or_tag);
3076+
if (current->hash_position_or_tag >= current->array->nNumUsed) {
3077+
return FAILURE;
3078+
}
3079+
}
3080+
}
3081+
3082+
return i > 0 ? SUCCESS : FAILURE;
3083+
}
3084+
3085+
/* Invariant: returned array is packed and has all UNDEF elements. */
3086+
static zend_array *spl_iterator_zip_reset_array(spl_zip_iterator *zip_iterator)
3087+
{
3088+
zval *array_zv = &zip_iterator->intern.data;
3089+
3090+
/* Reuse array if it's RC1 */
3091+
if (!Z_ISUNDEF_P(array_zv) && Z_REFCOUNT_P(array_zv) == 1) {
3092+
zend_array *array = Z_ARR_P(array_zv);
3093+
if (HT_IS_PACKED(array)
3094+
&& array->nNumUsed == zip_iterator->iterator_count
3095+
&& array->nNumOfElements == zip_iterator->iterator_count) {
3096+
array->nNextFreeElement = zip_iterator->iterator_count;
3097+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3098+
zval_ptr_dtor(&array->arPacked[i]);
3099+
ZVAL_UNDEF(&array->arPacked[i]);
3100+
}
3101+
return array;
3102+
}
3103+
}
3104+
3105+
zval_ptr_dtor(array_zv);
3106+
3107+
/* Create optimized packed array */
3108+
zend_array *array = zend_new_array(zip_iterator->iterator_count);
3109+
zend_hash_real_init_packed(array);
3110+
array->nNumUsed = array->nNumOfElements = array->nNextFreeElement = zip_iterator->iterator_count;
3111+
ZVAL_ARR(array_zv, array);
3112+
return array;
3113+
}
3114+
3115+
zval *spl_iterator_zip_get_current_data(zend_object_iterator *iter)
3116+
{
3117+
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
3118+
3119+
zend_array *array = spl_iterator_zip_reset_array(zip_iterator);
3120+
3121+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3122+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3123+
zval *data;
3124+
if (spl_zip_iterator_is_obj_entry(current)) {
3125+
data = current->obj_iter->funcs->get_current_data(current->obj_iter);
3126+
} else {
3127+
data = zend_hash_get_current_data_ex(current->array, &current->hash_position_or_tag);
3128+
}
3129+
if (UNEXPECTED(data == NULL)) {
3130+
for (uint32_t j = 0; j < i; j++) {
3131+
zval_ptr_dtor(&array->arPacked[j]);
3132+
ZVAL_UNDEF(&array->arPacked[j]);
3133+
}
3134+
return NULL;
3135+
}
3136+
ZVAL_COPY(&array->arPacked[i], data);
3137+
}
3138+
3139+
return &iter->data;
3140+
}
3141+
3142+
void spl_iterator_zip_move_forward(zend_object_iterator *iter)
3143+
{
3144+
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
3145+
3146+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3147+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3148+
if (spl_zip_iterator_is_obj_entry(current)) {
3149+
// TODO: error handling
3150+
current->obj_iter->funcs->move_forward(current->obj_iter);
3151+
} else {
3152+
// TODO: error handling
3153+
zend_hash_move_forward_ex(current->array, &current->hash_position_or_tag);
3154+
}
3155+
}
3156+
}
3157+
3158+
void spl_iterator_zip_rewind(zend_object_iterator *iter)
3159+
{
3160+
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
3161+
3162+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3163+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3164+
if (spl_zip_iterator_is_obj_entry(current)) {
3165+
if (current->obj_iter->funcs->rewind) {
3166+
current->obj_iter->funcs->rewind(current->obj_iter);
3167+
if (UNEXPECTED(EG(exception))) {
3168+
return;
3169+
}
3170+
} else if (iter->index > 0) {
3171+
zend_throw_error(NULL, "Iterator does not support rewinding because one or more sub iterators do not support rewinding");
3172+
return;
3173+
}
3174+
} else {
3175+
zend_hash_internal_pointer_reset_ex(current->array, &current->hash_position_or_tag);
3176+
}
3177+
}
3178+
}
3179+
3180+
static const zend_object_iterator_funcs spl_iterator_zip_funcs = {
3181+
spl_iterator_zip_dtor,
3182+
spl_iterator_zip_valid,
3183+
spl_iterator_zip_get_current_data,
3184+
NULL, /* get_current_key, uses default index implementation */
3185+
spl_iterator_zip_move_forward,
3186+
spl_iterator_zip_rewind, /* rewind */
3187+
NULL, /* invalidate_current */ // TODO ???
3188+
NULL, /* get_gc */ // TODO: do we need this? I suppose because it wraps potentially cyclic objects the answer is yes :-(
3189+
};
3190+
3191+
// TODO: by ref support ???
3192+
PHP_FUNCTION(iterator_zip)
3193+
{
3194+
zval *argv;
3195+
uint32_t iterator_count;
3196+
3197+
ZEND_PARSE_PARAMETERS_START(0, -1)
3198+
Z_PARAM_VARIADIC('*', argv, iterator_count)
3199+
ZEND_PARSE_PARAMETERS_END();
3200+
3201+
spl_zip_iterator_entry *iterators = safe_emalloc(iterator_count, sizeof(spl_zip_iterator_entry), 0);
3202+
3203+
for (uint32_t i = 0; i < iterator_count; i++) {
3204+
if (UNEXPECTED(!zend_is_iterable(&argv[i]))) {
3205+
for (uint32_t j = 0; j < i; j++) {
3206+
spl_zip_iterator_entry *current = &iterators[i];
3207+
if (spl_zip_iterator_is_obj_entry(current)) {
3208+
zend_iterator_dtor(current->obj_iter);
3209+
} else {
3210+
Z_TRY_DELREF_P(&argv[j]);
3211+
}
3212+
}
3213+
efree(iterators);
3214+
zend_argument_value_error(i + 1, "must be of type iterable, %s given", zend_zval_value_name(&argv[i]));
3215+
RETURN_THROWS();
3216+
}
3217+
3218+
if (Z_TYPE(argv[i]) == IS_ARRAY) {
3219+
iterators[i].hash_position_or_tag = 0;
3220+
iterators[i].array = Z_ARR(argv[i]);
3221+
Z_TRY_ADDREF(argv[i]);
3222+
} else {
3223+
ZEND_ASSERT(Z_TYPE(argv[i]) == IS_OBJECT);
3224+
3225+
zend_class_entry *ce = Z_OBJCE_P(&argv[i]);
3226+
zend_object_iterator *obj_iter = ce->get_iterator(ce, &argv[i], false);
3227+
iterators[i].hash_position_or_tag = UINT32_MAX;
3228+
iterators[i].obj_iter = obj_iter;
3229+
}
3230+
}
3231+
3232+
spl_zip_iterator *iterator = emalloc(sizeof(*iterator));
3233+
zend_iterator_init(&iterator->intern);
3234+
ZVAL_UNDEF(&iterator->intern.data);
3235+
3236+
iterator->intern.funcs = &spl_iterator_zip_funcs;
3237+
iterator->iterators = iterators;
3238+
iterator->iterator_count = iterator_count;
3239+
3240+
zend_create_internal_iterator_iter(return_value, &iterator->intern);
3241+
}
3242+
30293243
static int spl_iterator_count_apply(zend_object_iterator *iter, void *puser) /* {{{ */
30303244
{
30313245
if (UNEXPECTED(*(zend_long*)puser == ZEND_LONG_MAX)) {

ext/spl/tests/iterator_zip.phpt

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
--TEST--
2+
iterator_zip
3+
--FILE--
4+
<?php
5+
6+
foreach (iterator_zip() as $_) {}
7+
8+
foreach (iterator_zip([]) as $x) {
9+
var_dump($x);
10+
}
11+
12+
$a = [1, 2, 3];
13+
$b = [4, 5, 6];
14+
15+
foreach (iterator_zip($a, $b) as [$ai, $bi]) {
16+
var_dump($ai, $bi);
17+
echo "\n";
18+
}
19+
20+
function gen($i) {
21+
echo "in gen\n";
22+
yield $i;
23+
yield $i+1;
24+
yield $i+2;
25+
}
26+
27+
foreach (iterator_zip(gen(0), gen(3), gen(6), ["a","b","c"]) as $item) {
28+
var_dump($item);
29+
}
30+
31+
?>
32+
--EXPECT--
33+
int(1)
34+
int(4)
35+
36+
int(2)
37+
int(5)
38+
39+
int(3)
40+
int(6)
41+
42+
in gen
43+
in gen
44+
in gen
45+
array(4) {
46+
[0]=>
47+
int(0)
48+
[1]=>
49+
int(3)
50+
[2]=>
51+
int(6)
52+
[3]=>
53+
string(1) "a"
54+
}
55+
array(4) {
56+
[0]=>
57+
int(1)
58+
[1]=>
59+
int(4)
60+
[2]=>
61+
int(7)
62+
[3]=>
63+
string(1) "b"
64+
}
65+
array(4) {
66+
[0]=>
67+
int(2)
68+
[1]=>
69+
int(5)
70+
[2]=>
71+
int(8)
72+
[3]=>
73+
string(1) "c"
74+
}

0 commit comments

Comments
 (0)