Skip to content

Commit 6d7d812

Browse files
committed
Prototype iterator_zip
1 parent a7785e8 commit 6d7d812

File tree

6 files changed

+280
-2
lines changed

6 files changed

+280
-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: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3026,6 +3026,202 @@ 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+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3068+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3069+
if (spl_zip_iterator_is_obj_entry(current)) {
3070+
if (current->obj_iter->funcs->valid(current->obj_iter) != SUCCESS) {
3071+
return FAILURE;
3072+
}
3073+
} else {
3074+
current->hash_position_or_tag = zend_hash_get_current_pos_ex(current->array, current->hash_position_or_tag);
3075+
if (current->hash_position_or_tag >= current->array->nNumUsed) {
3076+
return FAILURE;
3077+
}
3078+
}
3079+
}
3080+
3081+
return SUCCESS;
3082+
}
3083+
3084+
static zend_array *spl_iterator_zip_reset_array(spl_zip_iterator *zip_iterator)
3085+
{
3086+
zval *array_zv = &zip_iterator->intern.data;
3087+
3088+
// TODO: optimize: reuse array if RC1
3089+
3090+
zval_ptr_dtor(array_zv);
3091+
3092+
/* Create optimized packed array */
3093+
zend_array *array = zend_new_array(zip_iterator->iterator_count);
3094+
zend_hash_real_init_packed(array);
3095+
array->nNumUsed = array->nNumOfElements = array->nNextFreeElement = zip_iterator->iterator_count;
3096+
ZVAL_ARR(array_zv, array);
3097+
return array;
3098+
}
3099+
3100+
zval *spl_iterator_zip_get_current_data(zend_object_iterator *iter)
3101+
{
3102+
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
3103+
3104+
zend_array *array = spl_iterator_zip_reset_array(zip_iterator);
3105+
3106+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3107+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3108+
zval *data;
3109+
if (spl_zip_iterator_is_obj_entry(current)) {
3110+
data = current->obj_iter->funcs->get_current_data(current->obj_iter);
3111+
} else {
3112+
data = zend_hash_get_current_data_ex(current->array, &current->hash_position_or_tag);
3113+
}
3114+
if (UNEXPECTED(data == NULL)) {
3115+
// TODO: cleanup the work we already did (and make sure no memory is uninit)
3116+
return NULL;
3117+
}
3118+
ZVAL_COPY(&array->arPacked[i], data);
3119+
}
3120+
3121+
return &iter->data;
3122+
}
3123+
3124+
void spl_iterator_zip_move_forward(zend_object_iterator *iter)
3125+
{
3126+
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
3127+
3128+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3129+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3130+
if (spl_zip_iterator_is_obj_entry(current)) {
3131+
// TODO: error handling
3132+
current->obj_iter->funcs->move_forward(current->obj_iter);
3133+
} else {
3134+
// TODO: error handling
3135+
zend_hash_move_forward_ex(current->array, &current->hash_position_or_tag);
3136+
}
3137+
}
3138+
}
3139+
3140+
void spl_iterator_zip_rewind(zend_object_iterator *iter)
3141+
{
3142+
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
3143+
3144+
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
3145+
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
3146+
if (spl_zip_iterator_is_obj_entry(current)) {
3147+
if (current->obj_iter->funcs->rewind) {
3148+
current->obj_iter->funcs->rewind(current->obj_iter);
3149+
if (UNEXPECTED(EG(exception))) {
3150+
return;
3151+
}
3152+
} else if (iter->index > 0) {
3153+
zend_throw_error(NULL, "Iterator does not support rewinding because one or more sub iterators do not support rewinding");
3154+
return;
3155+
}
3156+
} else {
3157+
zend_hash_internal_pointer_reset_ex(current->array, &current->hash_position_or_tag);
3158+
}
3159+
}
3160+
}
3161+
3162+
static const zend_object_iterator_funcs spl_iterator_zip_funcs = {
3163+
spl_iterator_zip_dtor,
3164+
spl_iterator_zip_valid,
3165+
spl_iterator_zip_get_current_data,
3166+
NULL, /* get_current_key, uses default index implementation */
3167+
spl_iterator_zip_move_forward,
3168+
spl_iterator_zip_rewind, /* rewind */
3169+
NULL, /* invalidate_current */ // TODO ???
3170+
NULL, /* get_gc */ // TODO: do we need this? I suppose because it wraps potentially cyclic objects the answer is yes :-(
3171+
};
3172+
3173+
// TODO: by ref support ???
3174+
PHP_FUNCTION(iterator_zip)
3175+
{
3176+
zval *argv;
3177+
uint32_t iterator_count;
3178+
3179+
ZEND_PARSE_PARAMETERS_START(0, -1)
3180+
Z_PARAM_VARIADIC('*', argv, iterator_count)
3181+
ZEND_PARSE_PARAMETERS_END();
3182+
3183+
spl_zip_iterator_entry *iterators = safe_emalloc(iterator_count, sizeof(spl_zip_iterator_entry), 0);
3184+
3185+
for (uint32_t i = 0; i < iterator_count; i++) {
3186+
if (UNEXPECTED(!zend_is_iterable(&argv[i]))) {
3187+
for (uint32_t j = 0; j < i; j++) {
3188+
spl_zip_iterator_entry *current = &iterators[i];
3189+
if (current->hash_position_or_tag == UINT32_MAX) {
3190+
zend_iterator_dtor(current->obj_iter);
3191+
} else {
3192+
Z_TRY_DELREF_P(&argv[j]);
3193+
}
3194+
}
3195+
efree(iterators);
3196+
zend_argument_value_error(i + 1, "must be of type iterable, %s given", zend_zval_value_name(&argv[i]));
3197+
RETURN_THROWS();
3198+
}
3199+
3200+
if (Z_TYPE(argv[i]) == IS_ARRAY) {
3201+
iterators[i].hash_position_or_tag = 0;
3202+
iterators[i].array = Z_ARR(argv[i]);
3203+
Z_TRY_ADDREF(argv[i]);
3204+
} else {
3205+
ZEND_ASSERT(Z_TYPE(argv[i]) == IS_OBJECT);
3206+
3207+
zend_class_entry *ce = Z_OBJCE_P(&argv[i]);
3208+
zend_object_iterator *obj_iter = ce->get_iterator(ce, &argv[i], false);
3209+
iterators[i].hash_position_or_tag = UINT32_MAX;
3210+
iterators[i].obj_iter = obj_iter;
3211+
}
3212+
}
3213+
3214+
spl_zip_iterator *iterator = emalloc(sizeof(*iterator));
3215+
zend_iterator_init(&iterator->intern);
3216+
ZVAL_UNDEF(&iterator->intern.data);
3217+
3218+
iterator->intern.funcs = &spl_iterator_zip_funcs;
3219+
iterator->iterators = iterators;
3220+
iterator->iterator_count = iterator_count;
3221+
3222+
zend_create_internal_iterator_iter(return_value, &iterator->intern);
3223+
}
3224+
30293225
static int spl_iterator_count_apply(zend_object_iterator *iter, void *puser) /* {{{ */
30303226
{
30313227
if (UNEXPECTED(*(zend_long*)puser == ZEND_LONG_MAX)) {

ext/spl/tests/iterator_zip.phpt

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

0 commit comments

Comments
 (0)