Skip to content

Commit 316ae46

Browse files
committed
Reduce string manipulation
1 parent e793c96 commit 316ae46

File tree

2 files changed

+47
-35
lines changed

2 files changed

+47
-35
lines changed

src/Internal/ArrayParser.php

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
*/
1212
final class ArrayParser
1313
{
14+
private const WHITESPACE = [" ", "\n", "\r", "\t", "\v", "\0"];
15+
1416
use ForbidCloning;
1517
use ForbidSerialization;
1618

@@ -25,12 +27,10 @@ final class ArrayParser
2527
*/
2628
public static function parse(string $data, \Closure $cast, string $delimiter = ','): array
2729
{
28-
$data = \trim($data);
29-
3030
$parser = new self($data, $cast, $delimiter);
3131
$result = $parser->parseToArray();
3232

33-
if ($parser->data !== '') {
33+
if (isset($parser->data[$parser->position])) {
3434
throw new PostgresParseException("Data left in buffer after parsing");
3535
}
3636

@@ -43,9 +43,10 @@ public static function parse(string $data, \Closure $cast, string $delimiter = '
4343
* @param string $delimiter Delimiter used to separate values.
4444
*/
4545
private function __construct(
46-
private string $data,
46+
private readonly string $data,
4747
private readonly \Closure $cast,
4848
private readonly string $delimiter = ',',
49+
private int $position = 0,
4950
) {
5051
}
5152

@@ -58,36 +59,35 @@ private function parseToArray(): array
5859
{
5960
$result = [];
6061

61-
if ($this->data === '') {
62-
throw new PostgresParseException("Unexpected end of data");
63-
}
62+
$this->position = $this->skipWhitespace($this->position);
6463

65-
if ($this->data[0] !== '{') {
64+
if (!isset($this->data[$this->position]) || $this->data[$this->position] !== '{') {
6665
throw new PostgresParseException("Missing opening bracket");
6766
}
6867

69-
$this->data = \ltrim(\substr($this->data, 1));
68+
$this->position = $this->skipWhitespace($this->position + 1);
7069

7170
do {
72-
if ($this->data === '') {
71+
if (!isset($this->data[$this->position])) {
7372
throw new PostgresParseException("Unexpected end of data");
7473
}
7574

76-
if ($this->data[0] === '}') { // Empty array
77-
$this->data = \ltrim(\substr($this->data, 1));
75+
if ($this->data[$this->position] === '}') { // Empty array
76+
$this->position = $this->skipWhitespace($this->position + 1);
7877
break;
7978
}
8079

81-
if ($this->data[0] === '{') { // Array
82-
$parser = new self($this->data, $this->cast, $this->delimiter);
80+
if ($this->data[$this->position] === '{') { // Array
81+
$parser = new self($this->data, $this->cast, $this->delimiter, $this->position);
8382
$result[] = $parser->parseToArray();
84-
$this->data = $parser->data;
85-
$end = $this->trim(0);
83+
$this->position = $parser->position;
84+
$delimiter = $this->moveToNextDelimiter($this->position);
8685
continue;
8786
}
8887

89-
if ($this->data[0] === '"') { // Quoted value
90-
for ($position = 1; isset($this->data[$position]); ++$position) {
88+
if ($this->data[$this->position] === '"') { // Quoted value
89+
++$this->position;
90+
for ($position = $this->position; isset($this->data[$position]); ++$position) {
9191
if ($this->data[$position] === '\\') {
9292
++$position; // Skip next character
9393
continue;
@@ -102,27 +102,30 @@ private function parseToArray(): array
102102
throw new PostgresParseException("Could not find matching quote in quoted value");
103103
}
104104

105-
$yield = \stripslashes(\substr($this->data, 1, $position - 1));
105+
$entry = \stripslashes(\substr($this->data, $this->position, $position - $this->position));
106106

107-
$end = $this->trim($position + 1);
107+
$delimiter = $this->moveToNextDelimiter($position + 1);
108108
} else { // Unquoted value
109-
$position = 0;
110-
while (isset($this->data[$position]) && $this->data[$position] !== $this->delimiter && $this->data[$position] !== '}') {
109+
$position = $this->position;
110+
while (isset($this->data[$position])
111+
&& $this->data[$position] !== $this->delimiter
112+
&& $this->data[$position] !== '}'
113+
) {
111114
++$position;
112115
}
113116

114-
$yield = \trim(\substr($this->data, 0, $position));
117+
$entry = \trim(\substr($this->data, $this->position, $position - $this->position));
115118

116-
$end = $this->trim($position);
119+
$delimiter = $this->moveToNextDelimiter($position);
117120

118-
if (\strcasecmp($yield, "NULL") === 0) { // Literal NULL is always unquoted.
121+
if (\strcasecmp($entry, "NULL") === 0) { // Literal NULL is always unquoted.
119122
$result[] = null;
120123
continue;
121124
}
122125
}
123126

124-
$result[] = ($this->cast)($yield);
125-
} while ($end !== '}');
127+
$result[] = ($this->cast)($entry);
128+
} while ($delimiter !== '}');
126129

127130
return $result;
128131
}
@@ -134,22 +137,31 @@ private function parseToArray(): array
134137
*
135138
* @throws PostgresParseException
136139
*/
137-
private function trim(int $position): string
140+
private function moveToNextDelimiter(int $position): string
138141
{
139-
$this->data = \ltrim(\substr($this->data, $position));
142+
$position = $this->skipWhitespace($position);
140143

141-
if ($this->data === '') {
144+
if (!isset($this->data[$position])) {
142145
throw new PostgresParseException("Unexpected end of data");
143146
}
144147

145-
$end = $this->data[0];
148+
$delimiter = $this->data[$position];
146149

147-
if ($end !== $this->delimiter && $end !== '}') {
150+
if ($delimiter !== $this->delimiter && $delimiter !== '}') {
148151
throw new PostgresParseException("Invalid delimiter");
149152
}
150153

151-
$this->data = \ltrim(\substr($this->data, 1));
154+
$this->position = $this->skipWhitespace($position + 1);
155+
156+
return $delimiter;
157+
}
158+
159+
private function skipWhitespace(int $position): int
160+
{
161+
while (isset($this->data[$position]) && \in_array($this->data[$position], self::WHITESPACE, true)) {
162+
++$position;
163+
}
152164

153-
return $end;
165+
return $position;
154166
}
155167
}

test/ArrayParserTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public function testMalformedNestedArray(): void
145145
public function testEmptyString(): void
146146
{
147147
$this->expectException(PostgresParseException::class);
148-
$this->expectExceptionMessage('Unexpected end of data');
148+
$this->expectExceptionMessage('Missing opening bracket');
149149

150150
$string = ' ';
151151
ArrayParser::parse($string, $this->getDefaultCastFunction());

0 commit comments

Comments
 (0)