mirror of
https://github.com/danog/byte-stream.git
synced 2024-11-27 04:14:49 +01:00
Add Parser
This commit is contained in:
parent
b1c3b9ce96
commit
c331b2138a
155
lib/Parser.php
Normal file
155
lib/Parser.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\ByteStream;
|
||||
|
||||
use Amp\{ Failure, InvalidYieldError, Loop, Promise, Success };
|
||||
|
||||
class Parser implements WritableStream {
|
||||
const CHUNK_SIZE = 8192;
|
||||
|
||||
/** @var \Generator */
|
||||
private $generator;
|
||||
|
||||
/** @var string */
|
||||
private $buffer = '';
|
||||
|
||||
/** @var int|string|null */
|
||||
private $delimiter;
|
||||
|
||||
/**
|
||||
* @param \Generator $generator
|
||||
*
|
||||
* @throws \Amp\InvalidYieldError If the generator yields an invalid value.
|
||||
*/
|
||||
public function __construct(\Generator $generator) {
|
||||
$this->generator = $generator;
|
||||
|
||||
$this->delimiter = $this->generator->current();
|
||||
|
||||
if (!$this->generator->valid()) {
|
||||
$this->dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->delimiter !== null
|
||||
&& (!\is_int($this->delimiter) || $this->delimiter <= 0)
|
||||
&& (!\is_string($this->delimiter) || !\strlen($this->delimiter))
|
||||
) {
|
||||
throw new InvalidYieldError(
|
||||
$generator,
|
||||
\sprintf(
|
||||
"Unexpected yield; Expected NULL, an int greater than 0, or a non-empty string; %s given",
|
||||
\is_object($this->delimiter) ? \sprintf("instance of %s", \get_class($this->delimiter)) : \gettype($this->delimiter)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the generator parser and returns any remaining data in the internal buffer. Writing data after calling
|
||||
* this method will result in an error.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function cancel(): string {
|
||||
$this->dispose();
|
||||
return $this->buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write(string $data): Promise {
|
||||
return $this->send($data, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function end(string $data = ""): Promise {
|
||||
return $this->send($data, true);
|
||||
}
|
||||
|
||||
private function send(string $data, bool $end = false): Promise {
|
||||
if ($this->generator === null) {
|
||||
throw new StreamException("The parser is no longer writable");
|
||||
}
|
||||
|
||||
$this->buffer .= $data;
|
||||
|
||||
try {
|
||||
while ($this->buffer !== "") {
|
||||
if (\is_int($this->delimiter)) {
|
||||
if (\strlen($this->buffer) < $this->delimiter) {
|
||||
break; // Too few bytes in buffer.
|
||||
}
|
||||
|
||||
$length = $this->delimiter;
|
||||
} elseif (\is_string($this->delimiter)) {
|
||||
if (($position = \strpos($this->buffer, $this->delimiter)) !== false) {
|
||||
$length = $position + \strlen($this->delimiter);
|
||||
} elseif ($end) { // Send remaining buffer to parser when ending.
|
||||
$length = \strlen($this->buffer);
|
||||
} else {
|
||||
break; // Delimiter not found in buffer.
|
||||
}
|
||||
} else {
|
||||
$length = \strlen($this->buffer);
|
||||
}
|
||||
|
||||
$send = \substr($this->buffer, 0, $length);
|
||||
$this->buffer = \substr($this->buffer, $length);
|
||||
|
||||
try {
|
||||
$this->delimiter = $this->generator->send($send);
|
||||
} catch (\Exception $exception) { // Wrap Exception instances into a StreamException.
|
||||
$end = true;
|
||||
throw new StreamException("The generator parser threw an exception", 0, $exception);
|
||||
} catch (\Throwable $exception) { // Rethrow Error instances.
|
||||
$end = true;
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if (!$this->generator->valid()) {
|
||||
$end = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->delimiter !== null
|
||||
&& (!\is_int($this->delimiter) || $this->delimiter <= 0)
|
||||
&& (!\is_string($this->delimiter) || !\strlen($this->delimiter))
|
||||
) {
|
||||
throw new InvalidYieldError(
|
||||
$this->generator,
|
||||
\sprintf(
|
||||
"Unexpected yield; Expected NULL, an int greater than 0, or a non-empty string; %s given",
|
||||
\is_object($this->delimiter) ? \sprintf("instance of %s", \get_class($this->delimiter)) : \gettype($this->delimiter)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new Success(\strlen($data));
|
||||
} catch (\Throwable $exception) {
|
||||
return new Failure($exception);
|
||||
} finally {
|
||||
if ($end) {
|
||||
$this->dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nulls the generator in a try/catch block and forwards any thrown exceptions from finally blocks to the loop
|
||||
* error handler.
|
||||
*/
|
||||
private function dispose() {
|
||||
try {
|
||||
$this->generator = null; // May throw from finally blocks.
|
||||
} catch (\Throwable $exception) {
|
||||
Loop::defer(function () use ($exception) {
|
||||
throw $exception;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user