private function parse($indent, $result = NULL, $key = NULL, $hasKey = FALSE)
{
$inlineParser = $indent === FALSE;
$value = NULL;
$hasValue = FALSE;
$tokens = $this->tokens;
$n =& $this->pos;
$count = count($tokens);
$mainResult =& $result;
for (; $n < $count; $n++) {
$t = $tokens[$n][0];
if ($t === ',') {
// ArrayEntry separator
if (!$hasKey && !$hasValue || !$inlineParser) {
$this->error();
}
$this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
$hasKey = $hasValue = FALSE;
} elseif ($t === ':' || $t === '=') {
// KeyValuePair separator
if ($hasValue && (is_array($value) || is_object($value))) {
$this->error('Unacceptable key');
} elseif ($hasKey && $key === NULL && $hasValue && !$inlineParser) {
$n++;
$result[] = $this->parse($indent . ' ', [], $value, TRUE);
$newIndent = isset($tokens[$n], $tokens[$n + 1]) ? (string) substr($tokens[$n][0], 1) : '';
// not last
if (strlen($newIndent) > strlen($indent)) {
$n++;
$this->error('Bad indentation');
} elseif (strlen($newIndent) < strlen($indent)) {
return $mainResult;
// block parser exit point
}
$hasKey = $hasValue = FALSE;
} elseif ($hasKey || !$hasValue) {
$this->error();
} else {
$key = (string) $value;
$hasKey = TRUE;
$hasValue = FALSE;
$result =& $mainResult;
}
} elseif ($t === '-') {
// BlockArray bullet
if ($hasKey || $hasValue || $inlineParser) {
$this->error();
}
$key = NULL;
$hasKey = TRUE;
} elseif (($tmp = self::BRACKETS) && isset($tmp[$t])) {
// Opening bracket [ ( {
if ($hasValue) {
if ($t !== '(') {
$this->error();
}
$n++;
if ($value instanceof Entity && $value->value === Neon::CHAIN) {
end($value->attributes)->attributes = $this->parse(FALSE, []);
} else {
$value = new Entity($value, $this->parse(FALSE, []));
}
} else {
$n++;
$value = $this->parse(FALSE, []);
}
$hasValue = TRUE;
if (!isset($tokens[$n]) || $tokens[$n][0] !== self::BRACKETS[$t]) {
// unexpected type of bracket or block-parser
$this->error();
}
} elseif ($t === ']' || $t === '}' || $t === ')') {
// Closing bracket ] ) }
if (!$inlineParser) {
$this->error();
}
break;
} elseif ($t[0] === "\n") {
// Indent
if ($inlineParser) {
if ($hasKey || $hasValue) {
$this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
$hasKey = $hasValue = FALSE;
}
} else {
while (isset($tokens[$n + 1]) && $tokens[$n + 1][0][0] === "\n") {
$n++;
// skip to last indent
}
if (!isset($tokens[$n + 1])) {
break;
}
$newIndent = (string) substr($tokens[$n][0], 1);
if ($indent === NULL) {
// first iteration
$indent = $newIndent;
}
$minlen = min(strlen($newIndent), strlen($indent));
if ($minlen && (string) substr($newIndent, 0, $minlen) !== (string) substr($indent, 0, $minlen)) {
$n++;
$this->error('Invalid combination of tabs and spaces');
}
if (strlen($newIndent) > strlen($indent)) {
// open new block-array or hash
if ($hasValue || !$hasKey) {
$n++;
$this->error('Bad indentation');
}
$this->addValue($result, $key, $this->parse($newIndent));
$newIndent = isset($tokens[$n], $tokens[$n + 1]) ? (string) substr($tokens[$n][0], 1) : '';
// not last
if (strlen($newIndent) > strlen($indent)) {
$n++;
$this->error('Bad indentation');
}
$hasKey = FALSE;
} else {
if ($hasValue && !$hasKey) {
// block items must have "key"; NULL key means list item
break;
} elseif ($hasKey) {
$this->addValue($result, $key, $hasValue ? $value : NULL);
if ($key !== NULL && !$hasValue && $newIndent === $indent && isset($tokens[$n + 1]) && $tokens[$n + 1][0] === '-') {
$result =& $result[$key];
}
$hasKey = $hasValue = FALSE;
}
}
if (strlen($newIndent) < strlen($indent)) {
// close block
return $mainResult;
// block parser exit point
}
}
} else {
// Value
if ($t[0] === '"' || $t[0] === "'") {
if (preg_match('#^...\\n+([\\t ]*)#', $t, $m)) {
$converted = substr($t, 3, -3);
$converted = str_replace("\n" . $m[1], "\n", $converted);
$converted = preg_replace('#^\\n|\\n[\\t ]*+\\z#', '', $converted);
} else {
$converted = substr($t, 1, -1);
}
if ($t[0] === '"') {
$converted = preg_replace_callback('#\\\\(?:ud[89ab][0-9a-f]{2}\\\\ud[c-f][0-9a-f]{2}|u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i', [$this, 'cbString'], $converted);
}
} elseif (($fix56 = self::SIMPLE_TYPES) && isset($fix56[$t]) && (!isset($tokens[$n + 1][0]) || $tokens[$n + 1][0] !== ':' && $tokens[$n + 1][0] !== '=')) {
$converted = constant(self::SIMPLE_TYPES[$t]);
} elseif (is_numeric($t)) {
$converted = $t * 1;
} elseif (preg_match(self::PATTERN_HEX, $t)) {
$converted = hexdec($t);
} elseif (preg_match(self::PATTERN_OCTAL, $t)) {
$converted = octdec($t);
} elseif (preg_match(self::PATTERN_BINARY, $t)) {
$converted = bindec($t);
} elseif (preg_match(self::PATTERN_DATETIME, $t)) {
$converted = new \DateTimeImmutable($t);
} else {
// literal
$converted = $t;
}
if ($hasValue) {
if ($value instanceof Entity) {
// Entity chaining
if ($value->value !== Neon::CHAIN) {
$value = new Entity(Neon::CHAIN, [$value]);
}
$value->attributes[] = new Entity($converted);
} else {
$this->error();
}
} else {
$value = $converted;
$hasValue = TRUE;
}
}
}
if ($inlineParser) {
if ($hasKey || $hasValue) {
$this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
}
} else {
if ($hasValue && !$hasKey) {
// block items must have "key"
if ($result === NULL) {
$result = $value;
// simple value parser
} else {
$this->error();
}
} elseif ($hasKey) {
$this->addValue($result, $key, $hasValue ? $value : NULL);
}
}
return $mainResult;
}