%PDF- %PDF-
| Direktori : /www/varak.net/losik.varak.net/vendor/nette/neon/src/Neon/ |
| Current File : /www/varak.net/losik.varak.net/vendor/nette/neon/src/Neon/Parser.php |
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Neon;
/** @internal */
final class Parser
{
/** @var TokenStream */
private $tokens;
/** @var int[] */
private $posToLine = [];
public function parse(TokenStream $tokens): Node
{
$this->tokens = $tokens;
$this->initLines();
while ($this->tokens->consume(Token::Newline));
$node = $this->parseBlock($this->tokens->getIndentation());
while ($this->tokens->consume(Token::Newline));
if ($this->tokens->isNext()) {
$this->tokens->error();
}
return $node;
}
private function parseBlock(string $indent, bool $onlyBullets = false): Node
{
$res = new Node\BlockArrayNode($indent);
$this->injectPos($res);
$keyCheck = [];
loop:
$item = new Node\ArrayItemNode;
$this->injectPos($item);
if ($this->tokens->consume('-')) {
// continue
} elseif (!$this->tokens->isNext() || $onlyBullets) {
return $res->items
? $res
: $this->injectPos(new Node\LiteralNode(null));
} else {
$value = $this->parseValue();
if ($this->tokens->consume(':', '=')) {
$this->checkArrayKey($value, $keyCheck);
$item->key = $value;
} else {
if ($res->items) {
$this->tokens->error();
}
return $value;
}
}
$res->items[] = $item;
$item->value = new Node\LiteralNode(null);
$this->injectPos($item->value);
if ($this->tokens->consume(Token::Newline)) {
while ($this->tokens->consume(Token::Newline));
$nextIndent = $this->tokens->getIndentation();
if (strncmp($nextIndent, $indent, min(strlen($nextIndent), strlen($indent)))) {
$this->tokens->error('Invalid combination of tabs and spaces');
} elseif (strlen($nextIndent) > strlen($indent)) { // open new block
$item->value = $this->parseBlock($nextIndent);
} elseif (strlen($nextIndent) < strlen($indent)) { // close block
return $res;
} elseif ($item->key !== null && $this->tokens->isNext('-')) { // special dash subblock
$item->value = $this->parseBlock($indent, true);
}
} elseif ($item->key === null) {
$item->value = $this->parseBlock($indent . ' '); // open new block after dash
} elseif ($this->tokens->isNext()) {
$item->value = $this->parseValue();
if ($this->tokens->isNext() && !$this->tokens->isNext(Token::Newline)) {
$this->tokens->error();
}
}
if ($item->value instanceof Node\BlockArrayNode) {
$item->value->indentation = substr($item->value->indentation, strlen($indent));
}
$this->injectPos($res, $res->startTokenPos, $item->value->endTokenPos);
$this->injectPos($item, $item->startTokenPos, $item->value->endTokenPos);
while ($this->tokens->consume(Token::Newline));
if (!$this->tokens->isNext()) {
return $res;
}
$nextIndent = $this->tokens->getIndentation();
if (strncmp($nextIndent, $indent, min(strlen($nextIndent), strlen($indent)))) {
$this->tokens->error('Invalid combination of tabs and spaces');
} elseif (strlen($nextIndent) > strlen($indent)) {
$this->tokens->error('Bad indentation');
} elseif (strlen($nextIndent) < strlen($indent)) { // close block
return $res;
}
goto loop;
}
private function parseValue(): Node
{
if ($token = $this->tokens->consume(Token::String)) {
try {
$node = new Node\StringNode(Node\StringNode::parse($token->value));
$this->injectPos($node, $this->tokens->getPos() - 1);
} catch (Exception $e) {
$this->tokens->error($e->getMessage(), $this->tokens->getPos() - 1);
}
} elseif ($token = $this->tokens->consume(Token::Literal)) {
$pos = $this->tokens->getPos() - 1;
$node = new Node\LiteralNode(Node\LiteralNode::parse($token->value, $this->tokens->isNext(':', '=')));
$this->injectPos($node, $pos);
} elseif ($this->tokens->isNext('[', '(', '{')) {
$node = $this->parseBraces();
} else {
$this->tokens->error();
}
return $this->parseEntity($node);
}
private function parseEntity(Node $node): Node
{
if (!$this->tokens->isNext('(')) {
return $node;
}
$attributes = $this->parseBraces();
$entities[] = $this->injectPos(new Node\EntityNode($node, $attributes->items), $node->startTokenPos, $attributes->endTokenPos);
while ($token = $this->tokens->consume(Token::Literal)) {
$valueNode = new Node\LiteralNode(Node\LiteralNode::parse($token->value));
$this->injectPos($valueNode, $this->tokens->getPos() - 1);
if ($this->tokens->isNext('(')) {
$attributes = $this->parseBraces();
$entities[] = $this->injectPos(new Node\EntityNode($valueNode, $attributes->items), $valueNode->startTokenPos, $attributes->endTokenPos);
} else {
$entities[] = $this->injectPos(new Node\EntityNode($valueNode), $valueNode->startTokenPos);
break;
}
}
return count($entities) === 1
? $entities[0]
: $this->injectPos(new Node\EntityChainNode($entities), $node->startTokenPos, end($entities)->endTokenPos);
}
private function parseBraces(): Node\InlineArrayNode
{
$token = $this->tokens->consume();
$endBrace = ['[' => ']', '{' => '}', '(' => ')'][$token->value];
$res = new Node\InlineArrayNode($token->value);
$this->injectPos($res, $this->tokens->getPos() - 1);
$keyCheck = [];
loop:
while ($this->tokens->consume(Token::Newline));
if ($this->tokens->consume($endBrace)) {
$this->injectPos($res, $res->startTokenPos, $this->tokens->getPos() - 1);
return $res;
}
$res->items[] = $item = new Node\ArrayItemNode;
$this->injectPos($item, $this->tokens->getPos());
$value = $this->parseValue();
if ($this->tokens->consume(':', '=')) {
$this->checkArrayKey($value, $keyCheck);
$item->key = $value;
$item->value = $this->tokens->isNext(Token::Newline, ',', $endBrace)
? $this->injectPos(new Node\LiteralNode(null), $this->tokens->getPos())
: $this->parseValue();
} else {
$item->value = $value;
}
$this->injectPos($item, $item->startTokenPos, $item->value->endTokenPos);
if ($this->tokens->consume(',', Token::Newline)) {
goto loop;
}
while ($this->tokens->consume(Token::Newline));
if (!$this->tokens->isNext($endBrace)) {
$this->tokens->error();
}
goto loop;
}
/** @param true[] $arr */
private function checkArrayKey(Node $key, array &$arr): void
{
if ((!$key instanceof Node\StringNode && !$key instanceof Node\LiteralNode) || !is_scalar($key->value)) {
$this->tokens->error('Unacceptable key', $key->startTokenPos);
}
$k = (string) $key->value;
if (array_key_exists($k, $arr)) {
$this->tokens->error("Duplicated key '$k'", $key->startTokenPos);
}
$arr[$k] = true;
}
private function injectPos(Node $node, int $start = null, int $end = null): Node
{
$node->startTokenPos = $start ?? $this->tokens->getPos();
$node->startLine = $this->posToLine[$node->startTokenPos];
$node->endTokenPos = $end ?? $node->startTokenPos;
$node->endLine = $this->posToLine[$node->endTokenPos + 1] ?? end($this->posToLine);
return $node;
}
private function initLines(): void
{
$this->posToLine = [];
$line = 1;
foreach ($this->tokens->getTokens() as $token) {
$this->posToLine[] = $line;
$line += substr_count($token->value, "\n");
}
$this->posToLine[] = $line;
}
}