Skip to content

Commit ef691f8

Browse files
committed
Parser: decodeString() moved to StringNode, literalToValue() moved to LiteralNode
1 parent 0e3ba03 commit ef691f8

File tree

3 files changed

+92
-85
lines changed

3 files changed

+92
-85
lines changed

src/Neon/Node/LiteralNode.php

+43
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@
1515
/** @internal */
1616
final class LiteralNode extends Node
1717
{
18+
private const SIMPLE_TYPES = [
19+
'true' => true, 'True' => true, 'TRUE' => true, 'yes' => true, 'Yes' => true, 'YES' => true, 'on' => true, 'On' => true, 'ON' => true,
20+
'false' => false, 'False' => false, 'FALSE' => false, 'no' => false, 'No' => false, 'NO' => false, 'off' => false, 'Off' => false, 'OFF' => false,
21+
'null' => null, 'Null' => null, 'NULL' => null,
22+
];
23+
24+
private const DEPRECATED_TYPES = ['on' => 1, 'On' => 1, 'ON' => 1, 'off' => 1, 'Off' => 1, 'OFF' => 1];
25+
26+
private const PATTERN_DATETIME = '#\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| ++)\d\d?:\d\d:\d\d(?:\.\d*+)? *+(?:Z|[-+]\d\d?(?::?\d\d)?)?)?$#DA';
27+
private const PATTERN_HEX = '#0x[0-9a-fA-F]++$#DA';
28+
private const PATTERN_OCTAL = '#0o[0-7]++$#DA';
29+
private const PATTERN_BINARY = '#0b[0-1]++$#DA';
30+
1831
/** @var mixed */
1932
public $value;
2033

@@ -32,6 +45,36 @@ public function toValue()
3245
}
3346

3447

48+
/** @return mixed */
49+
public static function parse(string $value, bool $isKey = false)
50+
{
51+
if (!$isKey && array_key_exists($value, self::SIMPLE_TYPES)) {
52+
if (isset(self::DEPRECATED_TYPES[$value])) {
53+
trigger_error("Neon: keyword '$value' is deprecated, use true/yes or false/no.", E_USER_DEPRECATED);
54+
}
55+
return self::SIMPLE_TYPES[$value];
56+
57+
} elseif (is_numeric($value)) {
58+
return $value * 1;
59+
60+
} elseif (preg_match(self::PATTERN_HEX, $value)) {
61+
return hexdec($value);
62+
63+
} elseif (preg_match(self::PATTERN_OCTAL, $value)) {
64+
return octdec($value);
65+
66+
} elseif (preg_match(self::PATTERN_BINARY, $value)) {
67+
return bindec($value);
68+
69+
} elseif (!$isKey && preg_match(self::PATTERN_DATETIME, $value)) {
70+
return new \DateTimeImmutable($value);
71+
72+
} else {
73+
return $value;
74+
}
75+
}
76+
77+
3578
public function toString(): string
3679
{
3780
if ($this->value instanceof \DateTimeInterface) {

src/Neon/Node/StringNode.php

+42
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
/** @internal */
1717
final class StringNode extends Node
1818
{
19+
private const ESCAPE_SEQUENCES = [
20+
't' => "\t", 'n' => "\n", 'r' => "\r", 'f' => "\x0C", 'b' => "\x08", '"' => '"', '\\' => '\\', '/' => '/', '_' => "\u{A0}",
21+
];
22+
1923
/** @var string */
2024
public $value;
2125

@@ -33,6 +37,44 @@ public function toValue(): string
3337
}
3438

3539

40+
public static function parse(string $s): string
41+
{
42+
if (preg_match('#^...\n++([\t ]*+)#', $s, $m)) { // multiline
43+
$res = substr($s, 3, -3);
44+
$res = str_replace("\n" . $m[1], "\n", $res);
45+
$res = preg_replace('#^\n|\n[\t ]*+$#D', '', $res);
46+
} else {
47+
$res = substr($s, 1, -1);
48+
if ($s[0] === "'") {
49+
$res = str_replace("''", "'", $res);
50+
}
51+
}
52+
if ($s[0] === "'") {
53+
return $res;
54+
}
55+
return preg_replace_callback(
56+
'#\\\\(?:ud[89ab][0-9a-f]{2}\\\\ud[c-f][0-9a-f]{2}|u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i',
57+
function (array $m): string {
58+
$sq = $m[0];
59+
if (isset(self::ESCAPE_SEQUENCES[$sq[1]])) {
60+
return self::ESCAPE_SEQUENCES[$sq[1]];
61+
} elseif ($sq[1] === 'u' && strlen($sq) >= 6) {
62+
if (($res = json_decode('"' . $sq . '"')) !== null) {
63+
return $res;
64+
}
65+
throw new Nette\Neon\Exception("Invalid UTF-8 sequence $sq");
66+
} elseif ($sq[1] === 'x' && strlen($sq) === 4) {
67+
trigger_error("Neon: '$sq' is deprecated, use '\\uXXXX' instead.", E_USER_DEPRECATED);
68+
return chr(hexdec(substr($sq, 2)));
69+
} else {
70+
throw new Nette\Neon\Exception("Invalid escaping sequence $sq");
71+
}
72+
},
73+
$res
74+
);
75+
}
76+
77+
3678
public function toString(): string
3779
{
3880
$res = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

src/Neon/Parser.php

+7-85
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,6 @@
1313
/** @internal */
1414
final class Parser
1515
{
16-
private const PATTERN_DATETIME = '#\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| ++)\d\d?:\d\d:\d\d(?:\.\d*+)? *+(?:Z|[-+]\d\d?(?::?\d\d)?)?)?$#DA';
17-
private const PATTERN_HEX = '#0x[0-9a-fA-F]++$#DA';
18-
private const PATTERN_OCTAL = '#0o[0-7]++$#DA';
19-
private const PATTERN_BINARY = '#0b[0-1]++$#DA';
20-
21-
private const SIMPLE_TYPES = [
22-
'true' => true, 'True' => true, 'TRUE' => true, 'yes' => true, 'Yes' => true, 'YES' => true, 'on' => true, 'On' => true, 'ON' => true,
23-
'false' => false, 'False' => false, 'FALSE' => false, 'no' => false, 'No' => false, 'NO' => false, 'off' => false, 'Off' => false, 'OFF' => false,
24-
'null' => null, 'Null' => null, 'NULL' => null,
25-
];
26-
27-
private const DEPRECATED_TYPES = ['on' => 1, 'On' => 1, 'ON' => 1, 'off' => 1, 'Off' => 1, 'OFF' => 1];
28-
29-
private const ESCAPE_SEQUENCES = [
30-
't' => "\t", 'n' => "\n", 'r' => "\r", 'f' => "\x0C", 'b' => "\x08", '"' => '"', '\\' => '\\', '/' => '/', '_' => "\u{A0}",
31-
];
32-
3316
/** @var TokenStream */
3417
private $tokens;
3518

@@ -133,11 +116,15 @@ private function parseBlock(string $indent, bool $onlyBullets = false): Node
133116
private function parseValue(): Node
134117
{
135118
if ($token = $this->tokens->consume(Token::STRING)) {
136-
$node = new Node\StringNode($this->decodeString($token->value), $this->tokens->getPos() - 1);
119+
try {
120+
$node = new Node\StringNode(Node\StringNode::parse($token->value), $this->tokens->getPos() - 1);
121+
} catch (Exception $e) {
122+
$this->tokens->error($e->getMessage(), $this->tokens->getPos() - 1);
123+
}
137124

138125
} elseif ($token = $this->tokens->consume(Token::LITERAL)) {
139126
$pos = $this->tokens->getPos() - 1;
140-
$node = new Node\LiteralNode($this->literalToValue($token->value, $this->tokens->isNext(':', '=')), $pos);
127+
$node = new Node\LiteralNode(Node\LiteralNode::parse($token->value, $this->tokens->isNext(':', '=')), $pos);
141128

142129
} elseif ($this->tokens->isNext('[', '(', '{')) {
143130
$node = $this->parseBraces();
@@ -159,7 +146,7 @@ private function parseEntity(Node $node): Node
159146
$entities[] = new Node\EntityNode($node, $attributes->items, $node->startPos, $attributes->endPos);
160147

161148
while ($token = $this->tokens->consume(Token::LITERAL)) {
162-
$valueNode = new Node\LiteralNode($this->literalToValue($token->value), $this->tokens->getPos() - 1);
149+
$valueNode = new Node\LiteralNode(Node\LiteralNode::parse($token->value), $this->tokens->getPos() - 1);
163150
if ($this->tokens->isNext('(')) {
164151
$attributes = $this->parseBraces();
165152
$entities[] = new Node\EntityNode($valueNode, $attributes->items, $valueNode->startPos, $attributes->endPos);
@@ -213,41 +200,6 @@ private function parseBraces(): Node\ArrayNode
213200
}
214201

215202

216-
private function decodeString(string $s): string
217-
{
218-
if (preg_match('#^...\n++([\t ]*+)#', $s, $m)) { // multiline
219-
$res = substr($s, 3, -3);
220-
$res = str_replace("\n" . $m[1], "\n", $res);
221-
$res = preg_replace('#^\n|\n[\t ]*+$#D', '', $res);
222-
} else {
223-
$res = substr($s, 1, -1);
224-
if ($s[0] === "'") {
225-
$res = str_replace("''", "'", $res);
226-
}
227-
}
228-
if ($s[0] === '"') {
229-
$res = preg_replace_callback(
230-
'#\\\\(?:ud[89ab][0-9a-f]{2}\\\\ud[c-f][0-9a-f]{2}|u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i',
231-
function (array $m): string {
232-
$sq = $m[0];
233-
if (isset(self::ESCAPE_SEQUENCES[$sq[1]])) {
234-
return self::ESCAPE_SEQUENCES[$sq[1]];
235-
} elseif ($sq[1] === 'u' && strlen($sq) >= 6) {
236-
return json_decode('"' . $sq . '"') ?? $this->tokens->error("Invalid UTF-8 sequence $sq", $this->tokens->getPos() - 1);
237-
} elseif ($sq[1] === 'x' && strlen($sq) === 4) {
238-
trigger_error("Neon: '$sq' is deprecated, use '\\uXXXX' instead.", E_USER_DEPRECATED);
239-
return chr(hexdec(substr($sq, 2)));
240-
} else {
241-
$this->tokens->error("Invalid escaping sequence $sq", $this->tokens->getPos() - 1);
242-
}
243-
},
244-
$res
245-
);
246-
}
247-
return $res;
248-
}
249-
250-
251203
private function checkArrayKey(Node $key, array &$arr): void
252204
{
253205
if ((!$key instanceof Node\StringNode && !$key instanceof Node\LiteralNode) || !is_scalar($key->value)) {
@@ -259,34 +211,4 @@ private function checkArrayKey(Node $key, array &$arr): void
259211
}
260212
$arr[$k] = true;
261213
}
262-
263-
264-
/** @return mixed */
265-
public function literalToValue(string $value, bool $isKey = false)
266-
{
267-
if (!$isKey && array_key_exists($value, self::SIMPLE_TYPES)) {
268-
if (isset(self::DEPRECATED_TYPES[$value])) {
269-
trigger_error("Neon: keyword '$value' is deprecated, use true/yes or false/no.", E_USER_DEPRECATED);
270-
}
271-
return self::SIMPLE_TYPES[$value];
272-
273-
} elseif (is_numeric($value)) {
274-
return $value * 1;
275-
276-
} elseif (preg_match(self::PATTERN_HEX, $value)) {
277-
return hexdec($value);
278-
279-
} elseif (preg_match(self::PATTERN_OCTAL, $value)) {
280-
return octdec($value);
281-
282-
} elseif (preg_match(self::PATTERN_BINARY, $value)) {
283-
return bindec($value);
284-
285-
} elseif (!$isKey && preg_match(self::PATTERN_DATETIME, $value)) {
286-
return new \DateTimeImmutable($value);
287-
288-
} else {
289-
return $value;
290-
}
291-
}
292214
}

0 commit comments

Comments
 (0)