Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / drush / drush / internal-copy / Config / Yaml / Inline.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Drush\Internal\Config\Yaml;
13
14 use Drush\Internal\Config\Yaml\Exception\ParseException;
15 use Drush\Internal\Config\Yaml\Exception\DumpException;
16 use Drush\Internal\Config\Yaml\Tag\TaggedValue;
17
18 /**
19  * Inline implements a YAML parser/dumper for the YAML inline syntax.
20  *
21  * @author Fabien Potencier <fabien@symfony.com>
22  *
23  * @internal
24  */
25 class Inline
26 {
27     const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
28
29     public static $parsedLineNumber;
30
31     private static $exceptionOnInvalidType = false;
32     private static $objectSupport = false;
33     private static $objectForMap = false;
34     private static $constantSupport = false;
35
36     /**
37      * Converts a YAML string to a PHP value.
38      *
39      * @param string $value      A YAML string
40      * @param int    $flags      A bit field of PARSE_* constants to customize the YAML parser behavior
41      * @param array  $references Mapping of variable names to values
42      *
43      * @return mixed A PHP value
44      *
45      * @throws ParseException
46      */
47     public static function parse($value, $flags = 0, $references = array())
48     {
49         if (is_bool($flags)) {
50             @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);
51
52             if ($flags) {
53                 $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
54             } else {
55                 $flags = 0;
56             }
57         }
58
59         if (func_num_args() >= 3 && !is_array($references)) {
60             @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED);
61
62             if ($references) {
63                 $flags |= Yaml::PARSE_OBJECT;
64             }
65
66             if (func_num_args() >= 4) {
67                 @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED);
68
69                 if (func_get_arg(3)) {
70                     $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
71                 }
72             }
73
74             if (func_num_args() >= 5) {
75                 $references = func_get_arg(4);
76             } else {
77                 $references = array();
78             }
79         }
80
81         self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
82         self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
83         self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
84         self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags);
85
86         $value = trim($value);
87
88         if ('' === $value) {
89             return '';
90         }
91
92         if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
93             $mbEncoding = mb_internal_encoding();
94             mb_internal_encoding('ASCII');
95         }
96
97         $i = 0;
98         $tag = self::parseTag($value, $i, $flags);
99         switch ($value[$i]) {
100             case '[':
101                 $result = self::parseSequence($value, $flags, $i, $references);
102                 ++$i;
103                 break;
104             case '{':
105                 $result = self::parseMapping($value, $flags, $i, $references);
106                 ++$i;
107                 break;
108             default:
109                 $result = self::parseScalar($value, $flags, null, $i, null === $tag, $references);
110         }
111
112         if (null !== $tag) {
113             return new TaggedValue($tag, $result);
114         }
115
116         // some comments are allowed at the end
117         if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
118             throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)));
119         }
120
121         if (isset($mbEncoding)) {
122             mb_internal_encoding($mbEncoding);
123         }
124
125         return $result;
126     }
127
128     /**
129      * Dumps a given PHP variable to a YAML string.
130      *
131      * @param mixed $value The PHP variable to convert
132      * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
133      *
134      * @return string The YAML string representing the PHP value
135      *
136      * @throws DumpException When trying to dump PHP resource
137      */
138     public static function dump($value, $flags = 0)
139     {
140         if (is_bool($flags)) {
141             @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);
142
143             if ($flags) {
144                 $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE;
145             } else {
146                 $flags = 0;
147             }
148         }
149
150         if (func_num_args() >= 3) {
151             @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', E_USER_DEPRECATED);
152
153             if (func_get_arg(2)) {
154                 $flags |= Yaml::DUMP_OBJECT;
155             }
156         }
157
158         switch (true) {
159             case is_resource($value):
160                 if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
161                     throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
162                 }
163
164                 return 'null';
165             case $value instanceof \DateTimeInterface:
166                 return $value->format('c');
167             case is_object($value):
168                 if ($value instanceof TaggedValue) {
169                     return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
170                 }
171
172                 if (Yaml::DUMP_OBJECT & $flags) {
173                     return '!php/object:'.serialize($value);
174                 }
175
176                 if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
177                     return self::dumpArray($value, $flags & ~Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
178                 }
179
180                 if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
181                     throw new DumpException('Object support when dumping a YAML file has been disabled.');
182                 }
183
184                 return 'null';
185             case is_array($value):
186                 return self::dumpArray($value, $flags);
187             case null === $value:
188                 return 'null';
189             case true === $value:
190                 return 'true';
191             case false === $value:
192                 return 'false';
193             case ctype_digit($value):
194                 return is_string($value) ? "'$value'" : (int) $value;
195             case is_numeric($value):
196                 $locale = setlocale(LC_NUMERIC, 0);
197                 if (false !== $locale) {
198                     setlocale(LC_NUMERIC, 'C');
199                 }
200                 if (is_float($value)) {
201                     $repr = (string) $value;
202                     if (is_infinite($value)) {
203                         $repr = str_ireplace('INF', '.Inf', $repr);
204                     } elseif (floor($value) == $value && $repr == $value) {
205                         // Preserve float data type since storing a whole number will result in integer value.
206                         $repr = '!!float '.$repr;
207                     }
208                 } else {
209                     $repr = is_string($value) ? "'$value'" : (string) $value;
210                 }
211                 if (false !== $locale) {
212                     setlocale(LC_NUMERIC, $locale);
213                 }
214
215                 return $repr;
216             case '' == $value:
217                 return "''";
218             case self::isBinaryString($value):
219                 return '!!binary '.base64_encode($value);
220             case Escaper::requiresDoubleQuoting($value):
221                 return Escaper::escapeWithDoubleQuotes($value);
222             case Escaper::requiresSingleQuoting($value):
223             case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value):
224             case Parser::preg_match(self::getHexRegex(), $value):
225             case Parser::preg_match(self::getTimestampRegex(), $value):
226                 return Escaper::escapeWithSingleQuotes($value);
227             default:
228                 return $value;
229         }
230     }
231
232     /**
233      * Check if given array is hash or just normal indexed array.
234      *
235      * @internal
236      *
237      * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check
238      *
239      * @return bool true if value is hash array, false otherwise
240      */
241     public static function isHash($value)
242     {
243         if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
244             return true;
245         }
246
247         $expectedKey = 0;
248
249         foreach ($value as $key => $val) {
250             if ($key !== $expectedKey++) {
251                 return true;
252             }
253         }
254
255         return false;
256     }
257
258     /**
259      * Dumps a PHP array to a YAML string.
260      *
261      * @param array $value The PHP array to dump
262      * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
263      *
264      * @return string The YAML string representing the PHP array
265      */
266     private static function dumpArray($value, $flags)
267     {
268         // array
269         if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) {
270             $output = array();
271             foreach ($value as $val) {
272                 $output[] = self::dump($val, $flags);
273             }
274
275             return sprintf('[%s]', implode(', ', $output));
276         }
277
278         // hash
279         $output = array();
280         foreach ($value as $key => $val) {
281             $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
282         }
283
284         return sprintf('{ %s }', implode(', ', $output));
285     }
286
287     /**
288      * Parses a YAML scalar.
289      *
290      * @param string   $scalar
291      * @param int      $flags
292      * @param string[] $delimiters
293      * @param int      &$i
294      * @param bool     $evaluate
295      * @param array    $references
296      *
297      * @return string
298      *
299      * @throws ParseException When malformed inline YAML string is parsed
300      *
301      * @internal
302      */
303     public static function parseScalar($scalar, $flags = 0, $delimiters = null, &$i = 0, $evaluate = true, $references = array(), $legacyOmittedKeySupport = false)
304     {
305         if (in_array($scalar[$i], array('"', "'"))) {
306             // quoted scalar
307             $output = self::parseQuotedScalar($scalar, $i);
308
309             if (null !== $delimiters) {
310                 $tmp = ltrim(substr($scalar, $i), ' ');
311                 if (!in_array($tmp[0], $delimiters)) {
312                     throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
313                 }
314             }
315         } else {
316             // "normal" string
317             if (!$delimiters) {
318                 $output = substr($scalar, $i);
319                 $i += strlen($output);
320
321                 // remove comments
322                 if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
323                     $output = substr($output, 0, $match[0][1]);
324                 }
325             } elseif (Parser::preg_match('/^(.'.($legacyOmittedKeySupport ? '+' : '*').'?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
326                 $output = $match[1];
327                 $i += strlen($output);
328             } else {
329                 throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar));
330             }
331
332             // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
333             if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
334                 throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
335             }
336
337             if ($output && '%' === $output[0]) {
338                 @trigger_error(sprintf('Not quoting the scalar "%s" starting with the "%%" indicator character is deprecated since Symfony 3.1 and will throw a ParseException in 4.0 on line %d.', $output, self::$parsedLineNumber + 1), E_USER_DEPRECATED);
339             }
340
341             if ($evaluate) {
342                 $output = self::evaluateScalar($output, $flags, $references);
343             }
344         }
345
346         return $output;
347     }
348
349     /**
350      * Parses a YAML quoted scalar.
351      *
352      * @param string $scalar
353      * @param int    &$i
354      *
355      * @return string
356      *
357      * @throws ParseException When malformed inline YAML string is parsed
358      */
359     private static function parseQuotedScalar($scalar, &$i)
360     {
361         if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
362             throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)));
363         }
364
365         $output = substr($match[0], 1, strlen($match[0]) - 2);
366
367         $unescaper = new Unescaper();
368         if ('"' == $scalar[$i]) {
369             $output = $unescaper->unescapeDoubleQuotedString($output);
370         } else {
371             $output = $unescaper->unescapeSingleQuotedString($output);
372         }
373
374         $i += strlen($match[0]);
375
376         return $output;
377     }
378
379     /**
380      * Parses a YAML sequence.
381      *
382      * @param string $sequence
383      * @param int    $flags
384      * @param int    &$i
385      * @param array  $references
386      *
387      * @return array
388      *
389      * @throws ParseException When malformed inline YAML string is parsed
390      */
391     private static function parseSequence($sequence, $flags, &$i = 0, $references = array())
392     {
393         $output = array();
394         $len = strlen($sequence);
395         ++$i;
396
397         // [foo, bar, ...]
398         while ($i < $len) {
399             if (']' === $sequence[$i]) {
400                 return $output;
401             }
402             if (',' === $sequence[$i] || ' ' === $sequence[$i]) {
403                 ++$i;
404
405                 continue;
406             }
407
408             $tag = self::parseTag($sequence, $i, $flags);
409             switch ($sequence[$i]) {
410                 case '[':
411                     // nested sequence
412                     $value = self::parseSequence($sequence, $flags, $i, $references);
413                     break;
414                 case '{':
415                     // nested mapping
416                     $value = self::parseMapping($sequence, $flags, $i, $references);
417                     break;
418                 default:
419                     $isQuoted = in_array($sequence[$i], array('"', "'"));
420                     $value = self::parseScalar($sequence, $flags, array(',', ']'), $i, null === $tag, $references);
421
422                     // the value can be an array if a reference has been resolved to an array var
423                     if (is_string($value) && !$isQuoted && false !== strpos($value, ': ')) {
424                         // embedded mapping?
425                         try {
426                             $pos = 0;
427                             $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
428                         } catch (\InvalidArgumentException $e) {
429                             // no, it's not
430                         }
431                     }
432
433                     --$i;
434             }
435
436             if (null !== $tag) {
437                 $value = new TaggedValue($tag, $value);
438             }
439
440             $output[] = $value;
441
442             ++$i;
443         }
444
445         throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence));
446     }
447
448     /**
449      * Parses a YAML mapping.
450      *
451      * @param string $mapping
452      * @param int    $flags
453      * @param int    &$i
454      * @param array  $references
455      *
456      * @return array|\stdClass
457      *
458      * @throws ParseException When malformed inline YAML string is parsed
459      */
460     private static function parseMapping($mapping, $flags, &$i = 0, $references = array())
461     {
462         $output = array();
463         $len = strlen($mapping);
464         ++$i;
465         $allowOverwrite = false;
466
467         // {foo: bar, bar:foo, ...}
468         while ($i < $len) {
469             switch ($mapping[$i]) {
470                 case ' ':
471                 case ',':
472                     ++$i;
473                     continue 2;
474                 case '}':
475                     if (self::$objectForMap) {
476                         return (object) $output;
477                     }
478
479                     return $output;
480             }
481
482             // key
483             $isKeyQuoted = in_array($mapping[$i], array('"', "'"), true);
484             $key = self::parseScalar($mapping, $flags, array(':', ' '), $i, false, array(), true);
485
486             if (':' !== $key && false === $i = strpos($mapping, ':', $i)) {
487                 break;
488             }
489
490             if (':' === $key) {
491                 @trigger_error(sprintf('Omitting the key of a mapping is deprecated and will throw a ParseException in 4.0 on line %d.', self::$parsedLineNumber + 1), E_USER_DEPRECATED);
492             }
493
494             if (!(Yaml::PARSE_KEYS_AS_STRINGS & $flags)) {
495                 $evaluatedKey = self::evaluateScalar($key, $flags, $references);
496
497                 if ('' !== $key && $evaluatedKey !== $key && !is_string($evaluatedKey) && !is_int($evaluatedKey)) {
498                     @trigger_error(sprintf('Implicit casting of incompatible mapping keys to strings is deprecated since version 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead on line %d.', self::$parsedLineNumber + 1), E_USER_DEPRECATED);
499                 }
500             }
501
502             if (':' !== $key && !$isKeyQuoted && (!isset($mapping[$i + 1]) || !in_array($mapping[$i + 1], array(' ', ',', '[', ']', '{', '}'), true))) {
503                 @trigger_error(sprintf('Using a colon after an unquoted mapping key that is not followed by an indication character (i.e. " ", ",", "[", "]", "{", "}") is deprecated since version 3.2 and will throw a ParseException in 4.0 on line %d.', self::$parsedLineNumber + 1), E_USER_DEPRECATED);
504             }
505
506             if ('<<' === $key) {
507                 $allowOverwrite = true;
508             }
509
510             while ($i < $len) {
511                 if (':' === $mapping[$i] || ' ' === $mapping[$i]) {
512                     ++$i;
513
514                     continue;
515                 }
516
517                 $tag = self::parseTag($mapping, $i, $flags);
518                 switch ($mapping[$i]) {
519                     case '[':
520                         // nested sequence
521                         $value = self::parseSequence($mapping, $flags, $i, $references);
522                         // Spec: Keys MUST be unique; first one wins.
523                         // Parser cannot abort this mapping earlier, since lines
524                         // are processed sequentially.
525                         // But overwriting is allowed when a merge node is used in current block.
526                         if ('<<' === $key) {
527                             foreach ($value as $parsedValue) {
528                                 $output += $parsedValue;
529                             }
530                         } elseif ($allowOverwrite || !isset($output[$key])) {
531                             if (null !== $tag) {
532                                 $output[$key] = new TaggedValue($tag, $value);
533                             } else {
534                                 $output[$key] = $value;
535                             }
536                         } elseif (isset($output[$key])) {
537                             @trigger_error(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0 on line %d.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED);
538                         }
539                         break;
540                     case '{':
541                         // nested mapping
542                         $value = self::parseMapping($mapping, $flags, $i, $references);
543                         // Spec: Keys MUST be unique; first one wins.
544                         // Parser cannot abort this mapping earlier, since lines
545                         // are processed sequentially.
546                         // But overwriting is allowed when a merge node is used in current block.
547                         if ('<<' === $key) {
548                             $output += $value;
549                         } elseif ($allowOverwrite || !isset($output[$key])) {
550                             if (null !== $tag) {
551                                 $output[$key] = new TaggedValue($tag, $value);
552                             } else {
553                                 $output[$key] = $value;
554                             }
555                         } elseif (isset($output[$key])) {
556                             @trigger_error(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0 on line %d.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED);
557                         }
558                         break;
559                     default:
560                         $value = self::parseScalar($mapping, $flags, array(',', '}'), $i, null === $tag, $references);
561                         // Spec: Keys MUST be unique; first one wins.
562                         // Parser cannot abort this mapping earlier, since lines
563                         // are processed sequentially.
564                         // But overwriting is allowed when a merge node is used in current block.
565                         if ('<<' === $key) {
566                             $output += $value;
567                         } elseif ($allowOverwrite || !isset($output[$key])) {
568                             if (null !== $tag) {
569                                 $output[$key] = new TaggedValue($tag, $value);
570                             } else {
571                                 $output[$key] = $value;
572                             }
573                         } elseif (isset($output[$key])) {
574                             @trigger_error(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0 on line %d.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED);
575                         }
576                         --$i;
577                 }
578                 ++$i;
579
580                 continue 2;
581             }
582         }
583
584         throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping));
585     }
586
587     /**
588      * Evaluates scalars and replaces magic values.
589      *
590      * @param string $scalar
591      * @param int    $flags
592      * @param array  $references
593      *
594      * @return mixed The evaluated YAML string
595      *
596      * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
597      */
598     private static function evaluateScalar($scalar, $flags, $references = array())
599     {
600         $scalar = trim($scalar);
601         $scalarLower = strtolower($scalar);
602
603         if (0 === strpos($scalar, '*')) {
604             if (false !== $pos = strpos($scalar, '#')) {
605                 $value = substr($scalar, 1, $pos - 2);
606             } else {
607                 $value = substr($scalar, 1);
608             }
609
610             // an unquoted *
611             if (false === $value || '' === $value) {
612                 throw new ParseException('A reference must contain at least one character.');
613             }
614
615             if (!array_key_exists($value, $references)) {
616                 throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
617             }
618
619             return $references[$value];
620         }
621
622         switch (true) {
623             case 'null' === $scalarLower:
624             case '' === $scalar:
625             case '~' === $scalar:
626                 return;
627             case 'true' === $scalarLower:
628                 return true;
629             case 'false' === $scalarLower:
630                 return false;
631             case '!' === $scalar[0]:
632                 switch (true) {
633                     case 0 === strpos($scalar, '!str'):
634                         return (string) substr($scalar, 5);
635                     case 0 === strpos($scalar, '! '):
636                         return (int) self::parseScalar(substr($scalar, 2), $flags);
637                     case 0 === strpos($scalar, '!php/object:'):
638                         if (self::$objectSupport) {
639                             return unserialize(substr($scalar, 12));
640                         }
641
642                         if (self::$exceptionOnInvalidType) {
643                             throw new ParseException('Object support when parsing a YAML file has been disabled.');
644                         }
645
646                         return;
647                     case 0 === strpos($scalar, '!!php/object:'):
648                         if (self::$objectSupport) {
649                             @trigger_error(sprintf('The !!php/object tag to indicate dumped PHP objects is deprecated since version 3.1 and will be removed in 4.0. Use the !php/object tag instead on line %d.', self::$parsedLineNumber + 1), E_USER_DEPRECATED);
650
651                             return unserialize(substr($scalar, 13));
652                         }
653
654                         if (self::$exceptionOnInvalidType) {
655                             throw new ParseException('Object support when parsing a YAML file has been disabled.');
656                         }
657
658                         return;
659                     case 0 === strpos($scalar, '!php/const:'):
660                         if (self::$constantSupport) {
661                             if (defined($const = substr($scalar, 11))) {
662                                 return constant($const);
663                             }
664
665                             throw new ParseException(sprintf('The constant "%s" is not defined.', $const));
666                         }
667                         if (self::$exceptionOnInvalidType) {
668                             throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar));
669                         }
670
671                         return;
672                     case 0 === strpos($scalar, '!!float '):
673                         return (float) substr($scalar, 8);
674                     case 0 === strpos($scalar, '!!binary '):
675                         return self::evaluateBinaryScalar(substr($scalar, 9));
676                     default:
677                         @trigger_error(sprintf('Using the unquoted scalar value "%s" is deprecated since version 3.3 and will be considered as a tagged value in 4.0. You must quote it on line %d.', $scalar, self::$parsedLineNumber + 1), E_USER_DEPRECATED);
678                 }
679
680             // Optimize for returning strings.
681             // no break
682             case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]):
683                 switch (true) {
684                     case Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar):
685                         $scalar = str_replace('_', '', (string) $scalar);
686                         // omitting the break / return as integers are handled in the next case
687                         // no break
688                     case ctype_digit($scalar):
689                         $raw = $scalar;
690                         $cast = (int) $scalar;
691
692                         return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
693                     case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
694                         $raw = $scalar;
695                         $cast = (int) $scalar;
696
697                         return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
698                     case is_numeric($scalar):
699                     case Parser::preg_match(self::getHexRegex(), $scalar):
700                         $scalar = str_replace('_', '', $scalar);
701
702                         return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
703                     case '.inf' === $scalarLower:
704                     case '.nan' === $scalarLower:
705                         return -log(0);
706                     case '-.inf' === $scalarLower:
707                         return log(0);
708                     case Parser::preg_match('/^(-|\+)?[0-9][0-9,]*(\.[0-9_]+)?$/', $scalar):
709                     case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar):
710                         if (false !== strpos($scalar, ',')) {
711                             @trigger_error(sprintf('Using the comma as a group separator for floats is deprecated since version 3.2 and will be removed in 4.0 on line %d.', self::$parsedLineNumber + 1), E_USER_DEPRECATED);
712                         }
713
714                         return (float) str_replace(array(',', '_'), '', $scalar);
715                     case Parser::preg_match(self::getTimestampRegex(), $scalar):
716                         if (Yaml::PARSE_DATETIME & $flags) {
717                             // When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
718                             return new \DateTime($scalar, new \DateTimeZone('UTC'));
719                         }
720
721                         $timeZone = date_default_timezone_get();
722                         date_default_timezone_set('UTC');
723                         $time = strtotime($scalar);
724                         date_default_timezone_set($timeZone);
725
726                         return $time;
727                 }
728         }
729
730         return (string) $scalar;
731     }
732
733     /**
734      * @param string $value
735      * @param int    &$i
736      * @param int    $flags
737      *
738      * @return null|string
739      */
740     private static function parseTag($value, &$i, $flags)
741     {
742         if ('!' !== $value[$i]) {
743             return;
744         }
745
746         $tagLength = strcspn($value, " \t\n", $i + 1);
747         $tag = substr($value, $i + 1, $tagLength);
748
749         $nextOffset = $i + $tagLength + 1;
750         $nextOffset += strspn($value, ' ', $nextOffset);
751
752         // Is followed by a scalar
753         if (!isset($value[$nextOffset]) || !in_array($value[$nextOffset], array('[', '{'), true)) {
754             // Manage scalars in {@link self::evaluateScalar()}
755             return;
756         }
757
758         // Built-in tags
759         if ($tag && '!' === $tag[0]) {
760             throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag));
761         }
762
763         if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
764             $i = $nextOffset;
765
766             return $tag;
767         }
768
769         throw new ParseException(sprintf('Tags support is not enabled. Enable the `Yaml::PARSE_CUSTOM_TAGS` flag to use "!%s".', $tag));
770     }
771
772     /**
773      * @param string $scalar
774      *
775      * @return string
776      *
777      * @internal
778      */
779     public static function evaluateBinaryScalar($scalar)
780     {
781         $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));
782
783         if (0 !== (strlen($parsedBinaryData) % 4)) {
784             throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', strlen($parsedBinaryData)));
785         }
786
787         if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
788             throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData));
789         }
790
791         return base64_decode($parsedBinaryData, true);
792     }
793
794     private static function isBinaryString($value)
795     {
796         return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
797     }
798
799     /**
800      * Gets a regex that matches a YAML date.
801      *
802      * @return string The regular expression
803      *
804      * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
805      */
806     private static function getTimestampRegex()
807     {
808         return <<<EOF
809         ~^
810         (?P<year>[0-9][0-9][0-9][0-9])
811         -(?P<month>[0-9][0-9]?)
812         -(?P<day>[0-9][0-9]?)
813         (?:(?:[Tt]|[ \t]+)
814         (?P<hour>[0-9][0-9]?)
815         :(?P<minute>[0-9][0-9])
816         :(?P<second>[0-9][0-9])
817         (?:\.(?P<fraction>[0-9]*))?
818         (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
819         (?::(?P<tz_minute>[0-9][0-9]))?))?)?
820         $~x
821 EOF;
822     }
823
824     /**
825      * Gets a regex that matches a YAML number in hexadecimal notation.
826      *
827      * @return string
828      */
829     private static function getHexRegex()
830     {
831         return '~^0x[0-9a-f_]++$~i';
832     }
833 }