Skip to content

Commit

Permalink
Encoder uses AST (Node::toString() methods)
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Oct 19, 2021
1 parent d2e3c5c commit 33d262a
Show file tree
Hide file tree
Showing 10 changed files with 396 additions and 57 deletions.
100 changes: 43 additions & 57 deletions src/Neon/Encoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,73 +22,59 @@ final class Encoder
/**
* Returns the NEON representation of a value.
*/
public function encode($var, int $flags = 0): string
public function encode($val, int $flags = 0): string
{
if ($var instanceof \DateTimeInterface) {
return $var->format('Y-m-d H:i:s O');
$node = $this->valueToNode($val, (bool) ($flags & self::BLOCK));
return $node->toString();
}

} elseif ($var instanceof Entity) {
if ($var->value === Neon::CHAIN) {
return implode('', array_map([$this, 'encode'], $var->attributes));
}
return $this->encode($var->value) . '('
. (is_array($var->attributes) ? substr($this->encode($var->attributes), 1, -1) : '') . ')';
}

if (is_object($var)) {
$obj = $var;
$var = [];
foreach ($obj as $k => $v) {
$var[$k] = $v;
}
}
public function valueToNode($val, bool $blockMode = false): Node
{
if ($val instanceof \DateTimeInterface) {
return new Node\LiteralNode($val);

if (is_array($var)) {
$isList = !$var || array_keys($var) === range(0, count($var) - 1);
$s = '';
if ($flags & self::BLOCK) {
if (count($var) === 0) {
return '[]';
}
foreach ($var as $k => $v) {
$v = $this->encode($v, self::BLOCK);
$s .= ($isList ? '-' : $this->encode($k) . ':')
. (strpos($v, "\n") === false
? ' ' . $v . "\n"
: "\n" . preg_replace('#^(?=.)#m', "\t", $v) . (substr($v, -2, 1) === "\n" ? '' : "\n"));
}
return $s;

} else {
foreach ($var as $k => $v) {
$s .= ($isList ? '' : $this->encode($k) . ': ') . $this->encode($v) . ', ';
}
return ($isList ? '[' : '{') . substr($s, 0, -2) . ($isList ? ']' : '}');
} elseif ($val instanceof Entity && $val->value === Neon::CHAIN) {
$node = new Node\EntityChainNode;
foreach ($val->attributes as $entity) {
$node->chain[] = $this->valueToNode($entity, $blockMode);
}
return $node;

} elseif (is_string($var)) {
if (!Lexer::requiresDelimiters($var)) {
return $var;
}
} elseif ($val instanceof Entity) {
return new Node\EntityNode(
$this->valueToNode($val->value),
$this->arrayToNodes((array) $val->attributes)
);

$res = json_encode($var, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($res === false) {
throw new Exception('Invalid UTF-8 sequence: ' . $var);
}
if (strpos($var, "\n") !== false) {
$res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) {
return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0];
}, $res);
$res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""';
}
return $res;
} elseif (is_object($val) || is_array($val)) {
$node = new Node\ArrayNode($blockMode ? '' : null);
$node->items = $this->arrayToNodes($val, $blockMode);
return $node;

} elseif (is_float($var)) {
$var = json_encode($var);
return strpos($var, '.') === false ? $var . '.0' : $var;
} elseif (is_string($val) && Lexer::requiresDelimiters($val)) {
return new Node\StringNode($val);

} else {
return json_encode($var);
return new Node\LiteralNode($val);
}
}


private function arrayToNodes($val, bool $blockMode = false): array
{
$res = [];
$counter = 0;
$hide = true;
foreach ($val as $k => $v) {
$res[] = $item = new Node\ArrayItemNode;
$item->key = $hide && $k === $counter ? null : self::valueToNode($k);
$item->value = self::valueToNode($v, $blockMode);
if ($hide && is_int($k)) {
$hide = $k === $counter;
$counter = max($k + 1, $counter);
}
}
return $res;
}
}
3 changes: 3 additions & 0 deletions src/Neon/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ abstract class Node
abstract public function toValue();


abstract public function toString(): string;


/** @return self[] */
public function getSubNodes(): array
{
Expand Down
34 changes: 34 additions & 0 deletions src/Neon/Node/ArrayItemNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,46 @@ public static function itemsToArray(array $items): array
}


/** @param self[] $items */
public static function itemsToInlineString(array $items): string
{
$res = '';
foreach ($items as $item) {
$res .= ($res === '' ? '' : ', ')
. ($item->key ? $item->key->toString() . ': ' : '')
. $item->value->toString();
}
return $res;
}


/** @param self[] $items */
public static function itemsToBlockString(array $items): string
{
$res = '';
foreach ($items as $item) {
$v = $item->value->toString();
$res .= ($item->key ? $item->key->toString() . ':' : '-')
. (strpos($v, "\n") === false
? ' ' . $v . "\n"
: "\n" . preg_replace('#^(?=.)#m', "\t", $v) . (substr($v, -2, 1) === "\n" ? '' : "\n"));
}
return $res;
}


public function toValue()
{
throw new \LogicException;
}


public function toString(): string
{
throw new \LogicException;
}


public function getSubNodes(): array
{
return $this->key ? [$this->key, $this->value] : [$this->value];
Expand Down
16 changes: 16 additions & 0 deletions src/Neon/Node/ArrayNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ public function toValue(): array
}


public function toString(): string
{
if ($this->indent === null) {
$isList = !array_filter($this->items, function ($item) { return $item->key; });
$res = ArrayItemNode::itemsToInlineString($this->items);
return ($isList ? '[' : '{') . $res . ($isList ? ']' : '}');

} elseif (count($this->items) === 0) {
return '[]';

} else {
return ArrayItemNode::itemsToBlockString($this->items);
}
}


public function getSubNodes(): array
{
return $this->items;
Expand Down
6 changes: 6 additions & 0 deletions src/Neon/Node/EntityChainNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public function toValue(): Neon\Entity
}


public function toString(): string
{
return implode('', array_map(function ($entity) { return $entity->toString(); }, $this->chain));
}


public function getSubNodes(): array
{
return $this->chain;
Expand Down
9 changes: 9 additions & 0 deletions src/Neon/Node/EntityNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ public function toValue(): Entity
}


public function toString(): string
{
return $this->value->toString()
. '('
. ($this->attributes ? ArrayItemNode::itemsToInlineString($this->attributes) : '')
. ')';
}


public function getSubNodes(): array
{
return array_merge([$this->value], $this->attributes);
Expand Down
21 changes: 21 additions & 0 deletions src/Neon/Node/LiteralNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,25 @@ public function toValue()
{
return $this->value;
}


public function toString(): string
{
if ($this->value instanceof \DateTimeInterface) {
return $this->value->format('Y-m-d H:i:s O');

} elseif (is_string($this->value)) {
return $this->value;

} elseif (is_float($this->value)) {
$res = json_encode($this->value);
return strpos($res, '.') === false ? $res . '.0' : $res;

} elseif (is_int($this->value) || is_bool($this->value) || $this->value === null) {
return json_encode($this->value);

} else {
throw new \LogicException;
}
}
}
17 changes: 17 additions & 0 deletions src/Neon/Node/StringNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Nette\Neon\Node;

use Nette;
use Nette\Neon\Node;


Expand All @@ -30,4 +31,20 @@ public function toValue(): string
{
return $this->value;
}


public function toString(): string
{
$res = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($res === false) {
throw new Nette\Neon\Exception('Invalid UTF-8 sequence: ' . $this->value);
}
if (strpos($this->value, "\n") !== false) {
$res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) {
return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0];
}, $res);
$res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""';
}
return $res;
}
}
35 changes: 35 additions & 0 deletions tests/Neon/Encoder.nodes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/** @phpVersion 7.2 */

declare(strict_types=1);

use Nette\Neon;
use Nette\Neon\Entity;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


$input = [
'map' => ['a' => 'b', 'c' => 'd'],
'index' => ['a', 'b', 'c'],
'mixed' => ['a', 'b', 4 => 'c', 'd'],
'entity' => new Entity('ent', ['a', 'b']),
'chain' => new Entity(Neon\Neon::CHAIN, [
new Entity('first', ['a', 'b']),
new Entity('second'),
]),
'multiline' => "hello\nworld",
'date' => new DateTime('2016-06-03T19:00:00+02:00'),
];


$encoder = new Neon\Encoder;
$node = $encoder->valueToNode($input);

Assert::matchFile(
__DIR__ . '/fixtures/Encoder.nodes.txt',
preg_replace('~ #\d+~', '', Tracy\Dumper::toText($node))
);
Loading

0 comments on commit 33d262a

Please sign in to comment.