228 lines
6.0 KiB
PHP
228 lines
6.0 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @author Tassos.gr <info@tassos.gr>
|
|
* @link https://www.tassos.gr
|
|
* @copyright Copyright © 2024 Tassos All Rights Reserved
|
|
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
|
*/
|
|
|
|
namespace NRFramework\Parser;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
/**
|
|
* RingBuffer
|
|
*
|
|
* A circular buffer of fixed length.
|
|
* This class essentially implements a fixed-size FIFO stack but with
|
|
* "convenient" accessor methods compared to manually handling a vanilla PHP array.
|
|
*
|
|
* Used by NRFramework\Parser\Parser and NRFramework\Parser\Lexer.
|
|
*/
|
|
class RingBuffer implements \Countable, \ArrayAccess, \Iterator
|
|
{
|
|
/**
|
|
* Iterator position
|
|
* @var int
|
|
*/
|
|
protected $iterator_position = 0;
|
|
|
|
/**
|
|
* Position of the next element
|
|
* @var integer
|
|
*/
|
|
protected $position = 0;
|
|
|
|
/**
|
|
* Contents buffer
|
|
* @var \SplFixedArray
|
|
*/
|
|
protected $buffer;
|
|
|
|
/**
|
|
* Size of the ring buffer
|
|
* @var int
|
|
*/
|
|
protected $size;
|
|
|
|
/**
|
|
* RingBuffer constructor
|
|
*
|
|
* Handles arguments through 'func_get_args' (gotta love PHP)
|
|
*
|
|
* @param int $size Size of the ring buffer
|
|
* @param array $val Initial values
|
|
*/
|
|
public function __construct()
|
|
{
|
|
//argument checks
|
|
$argv = func_get_args();
|
|
$argc = count($argv);
|
|
|
|
switch($argc)
|
|
{
|
|
case 1:
|
|
// array
|
|
if (is_array($argv[0]))
|
|
{
|
|
$this ->size = count($argv[0]);
|
|
$this->buffer = \SplFixedArray::fromArray($argv[0]);
|
|
}
|
|
// size
|
|
else if (is_numeric($argv[0]))
|
|
{
|
|
if ($argv[0] < 1)
|
|
{
|
|
throw new \InvalidArgumentException('RingBuffer ctor: size must be greater than zero');
|
|
}
|
|
$size = (integer)$argv[0];
|
|
$this->buffer = \SplFixedArray::fromArray(array_fill(0, $size, null));
|
|
$this->size = $size;
|
|
}
|
|
else
|
|
{
|
|
throw new \InvalidArgumentException("RingBuffer ctor: arguments must be an array ,a numeric size or both");
|
|
}
|
|
break;
|
|
case 2:
|
|
if(is_array($argv[0]) && is_numeric($argv[1]))
|
|
{
|
|
if ($argv[1] < 1)
|
|
{
|
|
throw new \InvalidArgumentException('RingBuffer ctor: size must be greater than zero');
|
|
}
|
|
$arr_size = count($argv[0]);
|
|
$size = (integer)$argv[1];
|
|
if ($arr_size == $size)
|
|
{
|
|
$this->buffer = \SplFixedArray::fromArray($argv[0]);
|
|
$this->size = $size;
|
|
}
|
|
else if ($arr_size > $size)
|
|
{
|
|
$this->buffer = \SplFixedArray::fromArray(array_slice($argv[0], 0, $size));
|
|
$this->size = $size ;
|
|
}
|
|
else // $arr_size < $size
|
|
{
|
|
$this->buffer = \SplFixedArray::fromArray(array_merge($argv[0], array_fill(0, $size - $arr_size, null)));
|
|
$this->size = $size ;
|
|
$this->position = $arr_size ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new \InvalidArgumentException("RingBuffer ctor: arguments must be an array ,a numeric size or both");
|
|
}
|
|
break;
|
|
default:
|
|
throw new \InvalidArgumentException('RingBuffer ctor: no arguments given');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the internal buffer as an array
|
|
*
|
|
* @return \SplFixedArray
|
|
*/
|
|
public function buffer()
|
|
{
|
|
return $this->buffer;
|
|
}
|
|
|
|
/**
|
|
* 'Countable' interface methods
|
|
*/
|
|
|
|
/**
|
|
* Returns the size of the buffer
|
|
*
|
|
* @return int
|
|
*/
|
|
public function count() : int
|
|
{
|
|
return $this->size;
|
|
}
|
|
|
|
/**
|
|
* 'ArrayAccess' interface methods
|
|
*/
|
|
|
|
protected function offsetOf($offset)
|
|
{
|
|
return ($this->position + $offset) % $this->size;
|
|
}
|
|
|
|
|
|
public function offsetExists($offset) : bool
|
|
{
|
|
return ($offset >= 0) && ($offset < $this->size);
|
|
}
|
|
|
|
#[\ReturnTypeWillChange]
|
|
public function offsetGet($offset)
|
|
{
|
|
// if (!$this->offsetExists($offset))
|
|
if (($offset < 1) && ($offset >= $this->size))
|
|
{
|
|
throw new \OutOfBoundsException("RingBuffer: invalid offset $offset.");
|
|
}
|
|
return $this->buffer[($this->position + $offset) % $this->size];
|
|
}
|
|
|
|
public function offsetUnset($offset) : void
|
|
{
|
|
if (!$this->offsetExists($offset))
|
|
{
|
|
throw new \OutOfBoundsException("RingBuffer: invalid offset $offset.");
|
|
}
|
|
$this->buffer[$this->offsetOf($offset)] = null;
|
|
}
|
|
|
|
public function offsetSet($offset, $value) : void
|
|
{
|
|
if ($offset === null)
|
|
{
|
|
$this->buffer[$this->position] = $value;
|
|
$this->position = ($this->position + 1)%$this->size;
|
|
}
|
|
else if ($this->offsetExists($offset))
|
|
{
|
|
$this->buffer[$this->offsetOf($offset)] = $value;
|
|
}
|
|
else
|
|
{
|
|
throw new \OutOfBoundsException("RingBuffer: invalid offset $offset.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 'Iterator' interface methods
|
|
*/
|
|
public function rewind() : void
|
|
{
|
|
$this->iterator_position = 0;
|
|
}
|
|
|
|
public function current() : mixed
|
|
{
|
|
return $this->buffer[$this->offsetOf($this->iterator_position)];
|
|
}
|
|
|
|
public function key() : mixed
|
|
{
|
|
return $this->iterator_position;
|
|
}
|
|
|
|
public function next() : void
|
|
{
|
|
$this->iterator_position++;
|
|
}
|
|
|
|
public function valid() : bool
|
|
{
|
|
return ($this->iterator_position >= 0) && ($this->iterator_position < $this->size);
|
|
}
|
|
}
|