Skip to content

Commit

Permalink
Prototype iterator_zip
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsdos committed Dec 11, 2024
1 parent a7785e8 commit 6d7d812
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 2 deletions.
7 changes: 6 additions & 1 deletion Zend/zend_interfaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -509,12 +509,17 @@ ZEND_API zend_result zend_create_internal_iterator_zval(zval *return_value, zval
return FAILURE;
}

zend_create_internal_iterator_iter(return_value, iter);
return SUCCESS;
}

ZEND_API void zend_create_internal_iterator_iter(zval *return_value, zend_object_iterator *iter)
{
zend_internal_iterator *intern =
(zend_internal_iterator *) zend_internal_iterator_create(zend_ce_internal_iterator);
intern->iter = iter;
intern->iter->index = 0;
ZVAL_OBJ(return_value, &intern->std);
return SUCCESS;
}

static void zend_internal_iterator_free(zend_object *obj) {
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_interfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ ZEND_API int zend_user_serialize(zval *object, unsigned char **buffer, size_t *b
ZEND_API int zend_user_unserialize(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);

ZEND_API zend_result zend_create_internal_iterator_zval(zval *return_value, zval *obj);
ZEND_API void zend_create_internal_iterator_iter(zval *return_value, zend_object_iterator *iter);

END_EXTERN_C()

Expand Down
2 changes: 2 additions & 0 deletions ext/spl/php_spl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@ function iterator_apply(Traversable $iterator, callable $callback, ?array $args
function iterator_count(iterable $iterator): int {}

function iterator_to_array(iterable $iterator, bool $preserve_keys = true): array {}

function iterator_zip(iterable... $iterators): InternalIterator {}
8 changes: 7 additions & 1 deletion ext/spl/php_spl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

196 changes: 196 additions & 0 deletions ext/spl/spl_iterators.c
Original file line number Diff line number Diff line change
Expand Up @@ -3026,6 +3026,202 @@ PHP_FUNCTION(iterator_to_array)
spl_iterator_apply(obj, use_keys ? spl_iterator_to_array_apply : spl_iterator_to_values_apply, (void*)return_value);
} /* }}} */

typedef struct {
HashPosition hash_position_or_tag; /* uses the fact that index UINT32_MAX is not possible for arrays */
union {
zend_array *array;
zend_object_iterator *obj_iter;
};
} spl_zip_iterator_entry;

typedef struct {
zend_object_iterator intern;
spl_zip_iterator_entry *iterators;
uint32_t iterator_count;
} spl_zip_iterator;

static zend_always_inline bool spl_zip_iterator_is_obj_entry(const spl_zip_iterator_entry *entry)
{
return entry->hash_position_or_tag == UINT32_MAX;
}

static void spl_iterator_zip_dtor(zend_object_iterator *iter)
{
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;
for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
if (spl_zip_iterator_is_obj_entry(current)) {
zend_iterator_dtor(current->obj_iter);
} else {
zend_array_release(current->array);
}
}
zval_ptr_dtor(&iter->data);
efree(zip_iterator->iterators);
}

static zend_result spl_iterator_zip_valid(zend_object_iterator *iter)
{
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;

for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
if (spl_zip_iterator_is_obj_entry(current)) {
if (current->obj_iter->funcs->valid(current->obj_iter) != SUCCESS) {
return FAILURE;
}
} else {
current->hash_position_or_tag = zend_hash_get_current_pos_ex(current->array, current->hash_position_or_tag);
if (current->hash_position_or_tag >= current->array->nNumUsed) {
return FAILURE;
}
}
}

return SUCCESS;
}

static zend_array *spl_iterator_zip_reset_array(spl_zip_iterator *zip_iterator)
{
zval *array_zv = &zip_iterator->intern.data;

// TODO: optimize: reuse array if RC1

zval_ptr_dtor(array_zv);

/* Create optimized packed array */
zend_array *array = zend_new_array(zip_iterator->iterator_count);
zend_hash_real_init_packed(array);
array->nNumUsed = array->nNumOfElements = array->nNextFreeElement = zip_iterator->iterator_count;
ZVAL_ARR(array_zv, array);
return array;
}

zval *spl_iterator_zip_get_current_data(zend_object_iterator *iter)
{
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;

zend_array *array = spl_iterator_zip_reset_array(zip_iterator);

for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
zval *data;
if (spl_zip_iterator_is_obj_entry(current)) {
data = current->obj_iter->funcs->get_current_data(current->obj_iter);
} else {
data = zend_hash_get_current_data_ex(current->array, &current->hash_position_or_tag);
}
if (UNEXPECTED(data == NULL)) {
// TODO: cleanup the work we already did (and make sure no memory is uninit)
return NULL;
}
ZVAL_COPY(&array->arPacked[i], data);
}

return &iter->data;
}

void spl_iterator_zip_move_forward(zend_object_iterator *iter)
{
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;

for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
if (spl_zip_iterator_is_obj_entry(current)) {
// TODO: error handling
current->obj_iter->funcs->move_forward(current->obj_iter);
} else {
// TODO: error handling
zend_hash_move_forward_ex(current->array, &current->hash_position_or_tag);
}
}
}

void spl_iterator_zip_rewind(zend_object_iterator *iter)
{
spl_zip_iterator *zip_iterator = (spl_zip_iterator *) iter;

for (uint32_t i = 0; i < zip_iterator->iterator_count; i++) {
spl_zip_iterator_entry *current = &zip_iterator->iterators[i];
if (spl_zip_iterator_is_obj_entry(current)) {
if (current->obj_iter->funcs->rewind) {
current->obj_iter->funcs->rewind(current->obj_iter);
if (UNEXPECTED(EG(exception))) {
return;
}
} else if (iter->index > 0) {
zend_throw_error(NULL, "Iterator does not support rewinding because one or more sub iterators do not support rewinding");
return;
}
} else {
zend_hash_internal_pointer_reset_ex(current->array, &current->hash_position_or_tag);
}
}
}

static const zend_object_iterator_funcs spl_iterator_zip_funcs = {
spl_iterator_zip_dtor,
spl_iterator_zip_valid,
spl_iterator_zip_get_current_data,
NULL, /* get_current_key, uses default index implementation */
spl_iterator_zip_move_forward,
spl_iterator_zip_rewind, /* rewind */
NULL, /* invalidate_current */ // TODO ???
NULL, /* get_gc */ // TODO: do we need this? I suppose because it wraps potentially cyclic objects the answer is yes :-(
};

// TODO: by ref support ???
PHP_FUNCTION(iterator_zip)
{
zval *argv;
uint32_t iterator_count;

ZEND_PARSE_PARAMETERS_START(0, -1)
Z_PARAM_VARIADIC('*', argv, iterator_count)
ZEND_PARSE_PARAMETERS_END();

spl_zip_iterator_entry *iterators = safe_emalloc(iterator_count, sizeof(spl_zip_iterator_entry), 0);

for (uint32_t i = 0; i < iterator_count; i++) {
if (UNEXPECTED(!zend_is_iterable(&argv[i]))) {
for (uint32_t j = 0; j < i; j++) {
spl_zip_iterator_entry *current = &iterators[i];
if (current->hash_position_or_tag == UINT32_MAX) {
zend_iterator_dtor(current->obj_iter);
} else {
Z_TRY_DELREF_P(&argv[j]);
}
}
efree(iterators);
zend_argument_value_error(i + 1, "must be of type iterable, %s given", zend_zval_value_name(&argv[i]));
RETURN_THROWS();
}

if (Z_TYPE(argv[i]) == IS_ARRAY) {
iterators[i].hash_position_or_tag = 0;
iterators[i].array = Z_ARR(argv[i]);
Z_TRY_ADDREF(argv[i]);
} else {
ZEND_ASSERT(Z_TYPE(argv[i]) == IS_OBJECT);

zend_class_entry *ce = Z_OBJCE_P(&argv[i]);
zend_object_iterator *obj_iter = ce->get_iterator(ce, &argv[i], false);
iterators[i].hash_position_or_tag = UINT32_MAX;
iterators[i].obj_iter = obj_iter;
}
}

spl_zip_iterator *iterator = emalloc(sizeof(*iterator));
zend_iterator_init(&iterator->intern);
ZVAL_UNDEF(&iterator->intern.data);

iterator->intern.funcs = &spl_iterator_zip_funcs;
iterator->iterators = iterators;
iterator->iterator_count = iterator_count;

zend_create_internal_iterator_iter(return_value, &iterator->intern);
}

static int spl_iterator_count_apply(zend_object_iterator *iter, void *puser) /* {{{ */
{
if (UNEXPECTED(*(zend_long*)puser == ZEND_LONG_MAX)) {
Expand Down
68 changes: 68 additions & 0 deletions ext/spl/tests/iterator_zip.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
--TEST--
iterator_zip
--FILE--
<?php

$a = [1, 2, 3];
$b = [4, 5, 6];

foreach (iterator_zip($a, $b) as [$ai, $bi]) {
var_dump($ai, $bi);
echo "\n";
}

function gen($i) {
echo "in gen\n";
yield $i;
yield $i+1;
yield $i+2;
}

foreach (iterator_zip(gen(0), gen(3), gen(6), ["a","b","c"]) as $item) {
var_dump($item);
}

?>
--EXPECT--
int(1)
int(4)

int(2)
int(5)

int(3)
int(6)

in gen
in gen
in gen
array(4) {
[0]=>
int(0)
[1]=>
int(3)
[2]=>
int(6)
[3]=>
string(1) "a"
}
array(4) {
[0]=>
int(1)
[1]=>
int(4)
[2]=>
int(7)
[3]=>
string(1) "b"
}
array(4) {
[0]=>
int(2)
[1]=>
int(5)
[2]=>
int(8)
[3]=>
string(1) "c"
}

0 comments on commit 6d7d812

Please sign in to comment.