3v4l.org

run code in 300+ PHP versions simultaneously
<?php class Token { public function __toString() { return get_called_class(); } } class ParensOpenToken extends Token {} class ParensCloseToken extends Token {} class SemicolonToken extends Token {} class ValueToken extends Token { public $value = ''; public function __toString() { return parent::__toString() . '("' . $this->value . '")'; } } class OtherToken extends Token { public $value = ''; public function __toString() { return parent::__toString() . '("' . $this->value . '")'; } } class Tokenizer { public function tokenize($string) { $tokensList = [ '[ \t\n]' => OtherToken::class, '\\{' => ParensOpenToken::class, '\\}' => ParensCloseToken::class, ';' => SemicolonToken::class, '[a-zA-Zа-яА-Я\-_0-9]+' => ValueToken::class ]; $tokens = []; while (mb_strlen($string) > 0) { $matched = false; foreach ($tokensList as $pattern => $token) { if (preg_match('/^(' . $pattern . ')/', $string, $out) !== FALSE) { if (isset($out[1])) { $token = new $token(); if ($token instanceof ValueToken && !is_null($out[1])) { $token->value = $out[1]; } $tokens[] = $token; $string = mb_substr($string, mb_strlen($out[1])); $matched = true; break; } } } if (!$matched) { $value = mb_substr($string, 0, 1); $otherToken = new OtherToken(); $otherToken->value = $value; $string = mb_substr($string, 1); } } return $tokens; } } class Parser { private $_tokens = []; private $_index = 0; private function isTokensAvailable() { return $this->_index < count($this->_tokens); } private function peekCurrentToken() { return $this->_tokens[$this->_index]; } private function popCurrentToken() { return $this->_tokens[$this->_index++]; } private function parsePrimary() { $peek = $this->peekCurrentToken(); if ($peek instanceof ParensOpenToken) { return $this->parseParens(); } elseif ($peek instanceof ValueToken) { return $this->parseValue(); } else { throw new \Exception('Parser error: unexpected character at index ' . $this->_index); return null; } } private function parseParens() { $token = $this->popCurrentToken(); if (!($token instanceof ParensOpenToken)) { throw new \Exception('Parser error: expected {'); } $expressions = []; while (true) { $expression = $this->parsePrimary(); $expressions[] = $expression; $token = $this->peekCurrentToken(); if ($token instanceof ParensCloseToken) { $this->popCurrentToken(); return $expressions; } elseif (!($token instanceof SemicolonToken)) { throw new \Exception('Parser error: expected semicolon after object in array'); } else { $this->popCurrentToken(); } } return $expressions; } private function parseValue() { $token = $this->popCurrentToken(); if (!($token instanceof ValueToken)) { throw new \Exception('Parser error: expected value token'); } return $token->value; } public function parse($tokens) { $result = []; $this->_tokens = $tokens; $this->_index = 0; while ($this->isTokensAvailable()) { $result[] = $this->parsePrimary(); } return $result; } } $tokenizer = new Tokenizer(); $parser = new Parser(); $string = '{{param1;{param2_array};param3;param4};{param5;{param6_array};param7;param8}}'; $tokens = $tokenizer->tokenize($string); $result = $parser->parse($tokens); print_r($result); $string2 = '{{param1;{param2_array;param2_array;param2_array};param3;param4}}'; $tokens2 = $tokenizer->tokenize($string2); $result2 = $parser->parse($tokens2); print_r($result2);
Output for 7.1.25 - 7.1.33, 7.2.0 - 7.2.33, 7.3.0 - 7.3.33, 7.4.0 - 7.4.26, 8.0.0 - 8.0.13, 8.1rc1 - rc3
Array ( [0] => Array ( [0] => Array ( [0] => param1 [1] => Array ( [0] => param2_array ) [2] => param3 [3] => param4 ) [1] => Array ( [0] => param5 [1] => Array ( [0] => param6_array ) [2] => param7 [3] => param8 ) ) ) Array ( [0] => Array ( [0] => Array ( [0] => param1 [1] => Array ( [0] => param2_array [1] => param2_array [2] => param2_array ) [2] => param3 [3] => param4 ) ) )

preferences:
56.95 ms | 539 KiB | 16 Q