diff --git a/ext/standard/Module.php b/ext/standard/Module.php index 738cf70ac2e..29d5a7bd366 100755 --- a/ext/standard/Module.php +++ b/ext/standard/Module.php @@ -72,9 +72,13 @@ public function getFunctions(): array new str_ends_with(), new strncmp(), new array_count(), + new array_count('sizeof'), new array_key_exists(), new in_array(), new array_push(), + new array_pop(), + new array_shift(), + new array_values(), new explode(), new implode(), ]; diff --git a/ext/standard/array_count.php b/ext/standard/array_count.php index 4ee2b0be6e9..b398539da79 100644 --- a/ext/standard/array_count.php +++ b/ext/standard/array_count.php @@ -23,9 +23,9 @@ */ final class array_count extends Internal { - public function __construct() + public function __construct(string $name = 'count') { - parent::__construct('count'); + parent::__construct($name); } public function execute(Frame $frame): void diff --git a/ext/standard/array_pop.php b/ext/standard/array_pop.php new file mode 100644 index 00000000000..f533ac45587 --- /dev/null +++ b/ext/standard/array_pop.php @@ -0,0 +1,53 @@ +calledArgs)) { + throw new \LogicException('array_pop() requires exactly one argument'); + } + $array = $frame->calledArgs[0]->resolveIndirect(); + if (Variable::TYPE_ARRAY !== $array->type) { + throw new \LogicException('array_pop() argument must be an array in this compiler build'); + } + $popped = $array->toArray()->popLast(); + if (null === $frame->returnVar) { + return; + } + if (null === $popped) { + $frame->returnVar->null(); + + return; + } + $frame->returnVar->copyFrom($popped); + } + + public Context $context; + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('array_pop() is not implemented for JIT in this compiler build'); + } +} diff --git a/ext/standard/array_shift.php b/ext/standard/array_shift.php new file mode 100644 index 00000000000..4276f2ab31b --- /dev/null +++ b/ext/standard/array_shift.php @@ -0,0 +1,53 @@ +calledArgs)) { + throw new \LogicException('array_shift() requires exactly one argument'); + } + $array = $frame->calledArgs[0]->resolveIndirect(); + if (Variable::TYPE_ARRAY !== $array->type) { + throw new \LogicException('array_shift() argument must be an array in this compiler build'); + } + $shifted = $array->toArray()->shiftFirst(); + if (null === $frame->returnVar) { + return; + } + if (null === $shifted) { + $frame->returnVar->null(); + + return; + } + $frame->returnVar->copyFrom($shifted); + } + + public Context $context; + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('array_shift() is not implemented for JIT in this compiler build'); + } +} diff --git a/ext/standard/array_values.php b/ext/standard/array_values.php new file mode 100644 index 00000000000..2483c01bfd5 --- /dev/null +++ b/ext/standard/array_values.php @@ -0,0 +1,47 @@ +calledArgs)) { + throw new \LogicException('array_values() requires exactly one argument'); + } + $array = $frame->calledArgs[0]->resolveIndirect(); + if (null === $frame->returnVar) { + return; + } + if (Variable::TYPE_ARRAY !== $array->type) { + throw new \LogicException('array_values() argument must be an array in this compiler build'); + } + $frame->returnVar->array($array->toArray()->valuesCopy()); + } + + public Context $context; + + public function call(Context $context, JITVariable ...$args): Value + { + throw new \LogicException('array_values() is not implemented for JIT in this compiler build'); + } +} diff --git a/lib/VM/HashTable.php b/lib/VM/HashTable.php index fc7dc730976..1e661d2a134 100755 --- a/lib/VM/HashTable.php +++ b/lib/VM/HashTable.php @@ -116,6 +116,79 @@ public function getNumElements(): int return $this->numElements; } + /** + * Remove and return the last element of a packed list array (no holes). + * Returns null when the array is empty. + */ + public function popLast(): ?Variable + { + $this->assertConsistent(); + if (0 === $this->numElements) { + return null; + } + if (!$this->isWithoutHoles()) { + throw new \LogicException('popLast() only supports packed list arrays without holes'); + } + $this->refcount->assertSeparated(); + $lastSlot = $this->numUsed - 1; + $bucket = $this->buckets->read($lastSlot); + $result = new Variable(); + $result->copyFrom($bucket->value->resolveIndirect()); + --$this->numUsed; + --$this->numElements; + --$this->nextFreeElement; + $this->rehash(); + + return $result; + } + + /** + * Remove and return the first element of a packed list array (no holes). + * Returns null when the array is empty. + */ + public function shiftFirst(): ?Variable + { + $this->assertConsistent(); + if (0 === $this->numElements) { + return null; + } + if (!$this->isWithoutHoles()) { + throw new \LogicException('shiftFirst() only supports packed list arrays without holes'); + } + $this->refcount->assertSeparated(); + $firstBucket = $this->buckets->read(0); + $result = new Variable(); + $result->copyFrom($firstBucket->value->resolveIndirect()); + for ($i = 0; $i < $this->numUsed - 1; ++$i) { + $src = $this->buckets->read($i + 1); + $dst = $this->buckets->read($i); + $dst->value->copyFrom($src->value); + $dst->hash = $i; + $dst->key = null; + } + --$this->numUsed; + --$this->numElements; + --$this->nextFreeElement; + $this->rehash(); + + return $result; + } + + /** + * Copy all defined values into a new packed list array. + */ + public function valuesCopy(): HashTable + { + $out = new self(); + foreach ($this->iterate(true) as $value) { + $copy = new Variable(); + $copy->copyFrom($value); + $out->append($copy); + } + + return $out; + } + public function hasKey(Variable $index): bool { $this->assertConsistent(); diff --git a/test/compliance/cases/stdlib/array_pop.phpt b/test/compliance/cases/stdlib/array_pop.phpt new file mode 100644 index 00000000000..9ae630153a0 --- /dev/null +++ b/test/compliance/cases/stdlib/array_pop.phpt @@ -0,0 +1,18 @@ +--TEST-- +stdlib array_pop() +--FILE-- +int($n); + $ht->append($var); + } + $popped = $ht->popLast(); + $this->assertNotNull($popped); + $this->assertSame(3, $popped->toInt()); + $this->assertSame(2, $ht->getNumElements()); + $this->assertNotNull($ht->findIndex(1)); + } + + public function testShiftFirst(): void + { + $ht = new HashTable(); + foreach ([10, 20] as $n) { + $var = new Variable(); + $var->int($n); + $ht->append($var); + } + $shifted = $ht->shiftFirst(); + $this->assertNotNull($shifted); + $this->assertSame(10, $shifted->toInt()); + $this->assertSame(1, $ht->getNumElements()); + $this->assertSame(20, $ht->findIndex(0)->toInt()); + } + + public function testValuesCopy(): void + { + $ht = new HashTable(); + foreach (['a', 'b'] as $s) { + $var = new Variable(); + $var->string($s); + $ht->append($var); + } + $copy = $ht->valuesCopy(); + $this->assertSame(2, $copy->getNumElements()); + $this->assertSame('a', $copy->findIndex(0)->toString()); + $this->assertSame('b', $copy->findIndex(1)->toString()); + } +}