1111 */
1212final 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}
0 commit comments