vendor/symfony/yaml/Inline.php line 262

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Yaml;
  11. use Symfony\Component\Yaml\Exception\DumpException;
  12. use Symfony\Component\Yaml\Exception\ParseException;
  13. use Symfony\Component\Yaml\Tag\TaggedValue;
  14. /**
  15.  * Inline implements a YAML parser/dumper for the YAML inline syntax.
  16.  *
  17.  * @author Fabien Potencier <fabien@symfony.com>
  18.  *
  19.  * @internal
  20.  */
  21. class Inline
  22. {
  23.     public const REGEX_QUOTED_STRING '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
  24.     public static int $parsedLineNumber = -1;
  25.     public static ?string $parsedFilename null;
  26.     private static bool $exceptionOnInvalidType false;
  27.     private static bool $objectSupport false;
  28.     private static bool $objectForMap false;
  29.     private static bool $constantSupport false;
  30.     public static function initialize(int $flagsint $parsedLineNumber nullstring $parsedFilename null)
  31.     {
  32.         self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE $flags);
  33.         self::$objectSupport = (bool) (Yaml::PARSE_OBJECT $flags);
  34.         self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP $flags);
  35.         self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT $flags);
  36.         self::$parsedFilename $parsedFilename;
  37.         if (null !== $parsedLineNumber) {
  38.             self::$parsedLineNumber $parsedLineNumber;
  39.         }
  40.     }
  41.     /**
  42.      * Converts a YAML string to a PHP value.
  43.      *
  44.      * @param int   $flags      A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior
  45.      * @param array $references Mapping of variable names to values
  46.      *
  47.      * @throws ParseException
  48.      */
  49.     public static function parse(string $value nullint $flags 0, array &$references = []): mixed
  50.     {
  51.         self::initialize($flags);
  52.         $value trim($value);
  53.         if ('' === $value) {
  54.             return '';
  55.         }
  56.         $i 0;
  57.         $tag self::parseTag($value$i$flags);
  58.         switch ($value[$i]) {
  59.             case '[':
  60.                 $result self::parseSequence($value$flags$i$references);
  61.                 ++$i;
  62.                 break;
  63.             case '{':
  64.                 $result self::parseMapping($value$flags$i$references);
  65.                 ++$i;
  66.                 break;
  67.             default:
  68.                 $result self::parseScalar($value$flagsnull$itrue$references);
  69.         }
  70.         // some comments are allowed at the end
  71.         if (preg_replace('/\s*#.*$/A'''substr($value$i))) {
  72.             throw new ParseException(sprintf('Unexpected characters near "%s".'substr($value$i)), self::$parsedLineNumber 1$valueself::$parsedFilename);
  73.         }
  74.         if (null !== $tag && '' !== $tag) {
  75.             return new TaggedValue($tag$result);
  76.         }
  77.         return $result;
  78.     }
  79.     /**
  80.      * Dumps a given PHP variable to a YAML string.
  81.      *
  82.      * @param mixed $value The PHP variable to convert
  83.      * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
  84.      *
  85.      * @throws DumpException When trying to dump PHP resource
  86.      */
  87.     public static function dump(mixed $valueint $flags 0): string
  88.     {
  89.         switch (true) {
  90.             case \is_resource($value):
  91.                 if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE $flags) {
  92.                     throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").'get_resource_type($value)));
  93.                 }
  94.                 return self::dumpNull($flags);
  95.             case $value instanceof \DateTimeInterface:
  96.                 return $value->format('c');
  97.             case $value instanceof \UnitEnum:
  98.                 return sprintf('!php/const %s::%s'$value::class, $value->name);
  99.             case \is_object($value):
  100.                 if ($value instanceof TaggedValue) {
  101.                     return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
  102.                 }
  103.                 if (Yaml::DUMP_OBJECT $flags) {
  104.                     return '!php/object '.self::dump(serialize($value));
  105.                 }
  106.                 if (Yaml::DUMP_OBJECT_AS_MAP $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
  107.                     return self::dumpHashArray($value$flags);
  108.                 }
  109.                 if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE $flags) {
  110.                     throw new DumpException('Object support when dumping a YAML file has been disabled.');
  111.                 }
  112.                 return self::dumpNull($flags);
  113.             case \is_array($value):
  114.                 return self::dumpArray($value$flags);
  115.             case null === $value:
  116.                 return self::dumpNull($flags);
  117.             case true === $value:
  118.                 return 'true';
  119.             case false === $value:
  120.                 return 'false';
  121.             case \is_int($value):
  122.                 return $value;
  123.             case is_numeric($value) && false === strpbrk($value"\f\n\r\t\v"):
  124.                 $locale setlocale(\LC_NUMERIC0);
  125.                 if (false !== $locale) {
  126.                     setlocale(\LC_NUMERIC'C');
  127.                 }
  128.                 if (\is_float($value)) {
  129.                     $repr = (string) $value;
  130.                     if (is_infinite($value)) {
  131.                         $repr str_ireplace('INF''.Inf'$repr);
  132.                     } elseif (floor($value) == $value && $repr == $value) {
  133.                         // Preserve float data type since storing a whole number will result in integer value.
  134.                         if (!str_contains($repr'E')) {
  135.                             $repr $repr.'.0';
  136.                         }
  137.                     }
  138.                 } else {
  139.                     $repr \is_string($value) ? "'$value'" : (string) $value;
  140.                 }
  141.                 if (false !== $locale) {
  142.                     setlocale(\LC_NUMERIC$locale);
  143.                 }
  144.                 return $repr;
  145.             case '' == $value:
  146.                 return "''";
  147.             case self::isBinaryString($value):
  148.                 return '!!binary '.base64_encode($value);
  149.             case Escaper::requiresDoubleQuoting($value):
  150.                 return Escaper::escapeWithDoubleQuotes($value);
  151.             case Escaper::requiresSingleQuoting($value):
  152.                 $singleQuoted Escaper::escapeWithSingleQuotes($value);
  153.                 if (!str_contains($value"'")) {
  154.                     return $singleQuoted;
  155.                 }
  156.                 // Attempt double-quoting the string instead to see if it's more efficient.
  157.                 $doubleQuoted Escaper::escapeWithDoubleQuotes($value);
  158.                 return \strlen($doubleQuoted) < \strlen($singleQuoted) ? $doubleQuoted $singleQuoted;
  159.             case Parser::preg_match('{^[0-9]+[_0-9]*$}'$value):
  160.             case Parser::preg_match(self::getHexRegex(), $value):
  161.             case Parser::preg_match(self::getTimestampRegex(), $value):
  162.                 return Escaper::escapeWithSingleQuotes($value);
  163.             default:
  164.                 return $value;
  165.         }
  166.     }
  167.     /**
  168.      * Check if given array is hash or just normal indexed array.
  169.      */
  170.     public static function isHash(array|\ArrayObject|\stdClass $value): bool
  171.     {
  172.         if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
  173.             return true;
  174.         }
  175.         $expectedKey 0;
  176.         foreach ($value as $key => $val) {
  177.             if ($key !== $expectedKey++) {
  178.                 return true;
  179.             }
  180.         }
  181.         return false;
  182.     }
  183.     /**
  184.      * Dumps a PHP array to a YAML string.
  185.      *
  186.      * @param array $value The PHP array to dump
  187.      * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
  188.      */
  189.     private static function dumpArray(array $valueint $flags): string
  190.     {
  191.         // array
  192.         if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE $flags) && !self::isHash($value)) {
  193.             $output = [];
  194.             foreach ($value as $val) {
  195.                 $output[] = self::dump($val$flags);
  196.             }
  197.             return sprintf('[%s]'implode(', '$output));
  198.         }
  199.         return self::dumpHashArray($value$flags);
  200.     }
  201.     /**
  202.      * Dumps hash array to a YAML string.
  203.      *
  204.      * @param array|\ArrayObject|\stdClass $value The hash array to dump
  205.      * @param int                          $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
  206.      */
  207.     private static function dumpHashArray(array|\ArrayObject|\stdClass $valueint $flags): string
  208.     {
  209.         $output = [];
  210.         foreach ($value as $key => $val) {
  211.             $output[] = sprintf('%s: %s'self::dump($key$flags), self::dump($val$flags));
  212.         }
  213.         return sprintf('{ %s }'implode(', '$output));
  214.     }
  215.     private static function dumpNull(int $flags): string
  216.     {
  217.         if (Yaml::DUMP_NULL_AS_TILDE $flags) {
  218.             return '~';
  219.         }
  220.         return 'null';
  221.     }
  222.     /**
  223.      * Parses a YAML scalar.
  224.      *
  225.      * @throws ParseException When malformed inline YAML string is parsed
  226.      */
  227.     public static function parseScalar(string $scalarint $flags 0, array $delimiters nullint &$i 0bool $evaluate true, array &$references = [], bool &$isQuoted null): mixed
  228.     {
  229.         if (\in_array($scalar[$i], ['"'"'"], true)) {
  230.             // quoted scalar
  231.             $isQuoted true;
  232.             $output self::parseQuotedScalar($scalar$i);
  233.             if (null !== $delimiters) {
  234.                 $tmp ltrim(substr($scalar$i), " \n");
  235.                 if ('' === $tmp) {
  236.                     throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".'implode(''$delimiters)), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  237.                 }
  238.                 if (!\in_array($tmp[0], $delimiters)) {
  239.                     throw new ParseException(sprintf('Unexpected characters (%s).'substr($scalar$i)), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  240.                 }
  241.             }
  242.         } else {
  243.             // "normal" string
  244.             $isQuoted false;
  245.             if (!$delimiters) {
  246.                 $output substr($scalar$i);
  247.                 $i += \strlen($output);
  248.                 // remove comments
  249.                 if (Parser::preg_match('/[ \t]+#/'$output$match\PREG_OFFSET_CAPTURE)) {
  250.                     $output substr($output0$match[0][1]);
  251.                 }
  252.             } elseif (Parser::preg_match('/^(.*?)('.implode('|'$delimiters).')/'substr($scalar$i), $match)) {
  253.                 $output $match[1];
  254.                 $i += \strlen($output);
  255.                 $output trim($output);
  256.             } else {
  257.                 throw new ParseException(sprintf('Malformed inline YAML string: "%s".'$scalar), self::$parsedLineNumber 1nullself::$parsedFilename);
  258.             }
  259.             // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
  260.             if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) {
  261.                 throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.'$output[0]), self::$parsedLineNumber 1$outputself::$parsedFilename);
  262.             }
  263.             if ($evaluate) {
  264.                 $output self::evaluateScalar($output$flags$references$isQuoted);
  265.             }
  266.         }
  267.         return $output;
  268.     }
  269.     /**
  270.      * Parses a YAML quoted scalar.
  271.      *
  272.      * @throws ParseException When malformed inline YAML string is parsed
  273.      */
  274.     private static function parseQuotedScalar(string $scalarint &$i 0): string
  275.     {
  276.         if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au'substr($scalar$i), $match)) {
  277.             throw new ParseException(sprintf('Malformed inline YAML string: "%s".'substr($scalar$i)), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  278.         }
  279.         $output substr($match[0], 1, -1);
  280.         $unescaper = new Unescaper();
  281.         if ('"' == $scalar[$i]) {
  282.             $output $unescaper->unescapeDoubleQuotedString($output);
  283.         } else {
  284.             $output $unescaper->unescapeSingleQuotedString($output);
  285.         }
  286.         $i += \strlen($match[0]);
  287.         return $output;
  288.     }
  289.     /**
  290.      * Parses a YAML sequence.
  291.      *
  292.      * @throws ParseException When malformed inline YAML string is parsed
  293.      */
  294.     private static function parseSequence(string $sequenceint $flagsint &$i 0, array &$references = []): array
  295.     {
  296.         $output = [];
  297.         $len \strlen($sequence);
  298.         ++$i;
  299.         // [foo, bar, ...]
  300.         while ($i $len) {
  301.             if (']' === $sequence[$i]) {
  302.                 return $output;
  303.             }
  304.             if (',' === $sequence[$i] || ' ' === $sequence[$i]) {
  305.                 ++$i;
  306.                 continue;
  307.             }
  308.             $tag self::parseTag($sequence$i$flags);
  309.             switch ($sequence[$i]) {
  310.                 case '[':
  311.                     // nested sequence
  312.                     $value self::parseSequence($sequence$flags$i$references);
  313.                     break;
  314.                 case '{':
  315.                     // nested mapping
  316.                     $value self::parseMapping($sequence$flags$i$references);
  317.                     break;
  318.                 default:
  319.                     $value self::parseScalar($sequence$flags, [','']'], $inull === $tag$references$isQuoted);
  320.                     // the value can be an array if a reference has been resolved to an array var
  321.                     if (\is_string($value) && !$isQuoted && str_contains($value': ')) {
  322.                         // embedded mapping?
  323.                         try {
  324.                             $pos 0;
  325.                             $value self::parseMapping('{'.$value.'}'$flags$pos$references);
  326.                         } catch (\InvalidArgumentException) {
  327.                             // no, it's not
  328.                         }
  329.                     }
  330.                     if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN$value$matches)) {
  331.                         $references[$matches['ref']] = $matches['value'];
  332.                         $value $matches['value'];
  333.                     }
  334.                     --$i;
  335.             }
  336.             if (null !== $tag && '' !== $tag) {
  337.                 $value = new TaggedValue($tag$value);
  338.             }
  339.             $output[] = $value;
  340.             ++$i;
  341.         }
  342.         throw new ParseException(sprintf('Malformed inline YAML string: "%s".'$sequence), self::$parsedLineNumber 1nullself::$parsedFilename);
  343.     }
  344.     /**
  345.      * Parses a YAML mapping.
  346.      *
  347.      * @throws ParseException When malformed inline YAML string is parsed
  348.      */
  349.     private static function parseMapping(string $mappingint $flagsint &$i 0, array &$references = []): array|\stdClass
  350.     {
  351.         $output = [];
  352.         $len \strlen($mapping);
  353.         ++$i;
  354.         $allowOverwrite false;
  355.         // {foo: bar, bar:foo, ...}
  356.         while ($i $len) {
  357.             switch ($mapping[$i]) {
  358.                 case ' ':
  359.                 case ',':
  360.                 case "\n":
  361.                     ++$i;
  362.                     continue 2;
  363.                 case '}':
  364.                     if (self::$objectForMap) {
  365.                         return (object) $output;
  366.                     }
  367.                     return $output;
  368.             }
  369.             // key
  370.             $offsetBeforeKeyParsing $i;
  371.             $isKeyQuoted \in_array($mapping[$i], ['"'"'"], true);
  372.             $key self::parseScalar($mapping$flags, [':'' '], $ifalse);
  373.             if ($offsetBeforeKeyParsing === $i) {
  374.                 throw new ParseException('Missing mapping key.'self::$parsedLineNumber 1$mapping);
  375.             }
  376.             if ('!php/const' === $key || '!php/enum' === $key) {
  377.                 $key .= ' '.self::parseScalar($mapping$flags, [':'], $ifalse);
  378.                 $key self::evaluateScalar($key$flags);
  379.             }
  380.             if (false === $i strpos($mapping':'$i)) {
  381.                 break;
  382.             }
  383.             if (!$isKeyQuoted) {
  384.                 $evaluatedKey self::evaluateScalar($key$flags$references);
  385.                 if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) {
  386.                     throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.'self::$parsedLineNumber 1$mapping);
  387.                 }
  388.             }
  389.             if (!$isKeyQuoted && (!isset($mapping[$i 1]) || !\in_array($mapping[$i 1], [' '',''['']''{''}'"\n"], true))) {
  390.                 throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").'self::$parsedLineNumber 1$mapping);
  391.             }
  392.             if ('<<' === $key) {
  393.                 $allowOverwrite true;
  394.             }
  395.             while ($i $len) {
  396.                 if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) {
  397.                     ++$i;
  398.                     continue;
  399.                 }
  400.                 $tag self::parseTag($mapping$i$flags);
  401.                 switch ($mapping[$i]) {
  402.                     case '[':
  403.                         // nested sequence
  404.                         $value self::parseSequence($mapping$flags$i$references);
  405.                         // Spec: Keys MUST be unique; first one wins.
  406.                         // Parser cannot abort this mapping earlier, since lines
  407.                         // are processed sequentially.
  408.                         // But overwriting is allowed when a merge node is used in current block.
  409.                         if ('<<' === $key) {
  410.                             foreach ($value as $parsedValue) {
  411.                                 $output += $parsedValue;
  412.                             }
  413.                         } elseif ($allowOverwrite || !isset($output[$key])) {
  414.                             if (null !== $tag) {
  415.                                 $output[$key] = new TaggedValue($tag$value);
  416.                             } else {
  417.                                 $output[$key] = $value;
  418.                             }
  419.                         } elseif (isset($output[$key])) {
  420.                             throw new ParseException(sprintf('Duplicate key "%s" detected.'$key), self::$parsedLineNumber 1$mapping);
  421.                         }
  422.                         break;
  423.                     case '{':
  424.                         // nested mapping
  425.                         $value self::parseMapping($mapping$flags$i$references);
  426.                         // Spec: Keys MUST be unique; first one wins.
  427.                         // Parser cannot abort this mapping earlier, since lines
  428.                         // are processed sequentially.
  429.                         // But overwriting is allowed when a merge node is used in current block.
  430.                         if ('<<' === $key) {
  431.                             $output += $value;
  432.                         } elseif ($allowOverwrite || !isset($output[$key])) {
  433.                             if (null !== $tag) {
  434.                                 $output[$key] = new TaggedValue($tag$value);
  435.                             } else {
  436.                                 $output[$key] = $value;
  437.                             }
  438.                         } elseif (isset($output[$key])) {
  439.                             throw new ParseException(sprintf('Duplicate key "%s" detected.'$key), self::$parsedLineNumber 1$mapping);
  440.                         }
  441.                         break;
  442.                     default:
  443.                         $value self::parseScalar($mapping$flags, [',''}'"\n"], $inull === $tag$references$isValueQuoted);
  444.                         // Spec: Keys MUST be unique; first one wins.
  445.                         // Parser cannot abort this mapping earlier, since lines
  446.                         // are processed sequentially.
  447.                         // But overwriting is allowed when a merge node is used in current block.
  448.                         if ('<<' === $key) {
  449.                             $output += $value;
  450.                         } elseif ($allowOverwrite || !isset($output[$key])) {
  451.                             if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN$value$matches)) {
  452.                                 $references[$matches['ref']] = $matches['value'];
  453.                                 $value $matches['value'];
  454.                             }
  455.                             if (null !== $tag) {
  456.                                 $output[$key] = new TaggedValue($tag$value);
  457.                             } else {
  458.                                 $output[$key] = $value;
  459.                             }
  460.                         } elseif (isset($output[$key])) {
  461.                             throw new ParseException(sprintf('Duplicate key "%s" detected.'$key), self::$parsedLineNumber 1$mapping);
  462.                         }
  463.                         --$i;
  464.                 }
  465.                 ++$i;
  466.                 continue 2;
  467.             }
  468.         }
  469.         throw new ParseException(sprintf('Malformed inline YAML string: "%s".'$mapping), self::$parsedLineNumber 1nullself::$parsedFilename);
  470.     }
  471.     /**
  472.      * Evaluates scalars and replaces magic values.
  473.      *
  474.      * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
  475.      */
  476.     private static function evaluateScalar(string $scalarint $flags, array &$references = [], bool &$isQuotedString null): mixed
  477.     {
  478.         $isQuotedString false;
  479.         $scalar trim($scalar);
  480.         if (str_starts_with($scalar'*')) {
  481.             if (false !== $pos strpos($scalar'#')) {
  482.                 $value substr($scalar1$pos 2);
  483.             } else {
  484.                 $value substr($scalar1);
  485.             }
  486.             // an unquoted *
  487.             if (false === $value || '' === $value) {
  488.                 throw new ParseException('A reference must contain at least one character.'self::$parsedLineNumber 1$valueself::$parsedFilename);
  489.             }
  490.             if (!\array_key_exists($value$references)) {
  491.                 throw new ParseException(sprintf('Reference "%s" does not exist.'$value), self::$parsedLineNumber 1$valueself::$parsedFilename);
  492.             }
  493.             return $references[$value];
  494.         }
  495.         $scalarLower strtolower($scalar);
  496.         switch (true) {
  497.             case 'null' === $scalarLower:
  498.             case '' === $scalar:
  499.             case '~' === $scalar:
  500.                 return null;
  501.             case 'true' === $scalarLower:
  502.                 return true;
  503.             case 'false' === $scalarLower:
  504.                 return false;
  505.             case '!' === $scalar[0]:
  506.                 switch (true) {
  507.                     case str_starts_with($scalar'!!str '):
  508.                         $s = (string) substr($scalar6);
  509.                         if (\in_array($s[0] ?? '', ['"'"'"], true)) {
  510.                             $isQuotedString true;
  511.                             $s self::parseQuotedScalar($s);
  512.                         }
  513.                         return $s;
  514.                     case str_starts_with($scalar'! '):
  515.                         return substr($scalar2);
  516.                     case str_starts_with($scalar'!php/object'):
  517.                         if (self::$objectSupport) {
  518.                             if (!isset($scalar[12])) {
  519.                                 throw new ParseException('Missing value for tag "!php/object".'self::$parsedLineNumber 1$scalarself::$parsedFilename);
  520.                             }
  521.                             return unserialize(self::parseScalar(substr($scalar12)));
  522.                         }
  523.                         if (self::$exceptionOnInvalidType) {
  524.                             throw new ParseException('Object support when parsing a YAML file has been disabled.'self::$parsedLineNumber 1$scalarself::$parsedFilename);
  525.                         }
  526.                         return null;
  527.                     case str_starts_with($scalar'!php/const'):
  528.                         if (self::$constantSupport) {
  529.                             if (!isset($scalar[11])) {
  530.                                 throw new ParseException('Missing value for tag "!php/const".'self::$parsedLineNumber 1$scalarself::$parsedFilename);
  531.                             }
  532.                             $i 0;
  533.                             if (\defined($const self::parseScalar(substr($scalar11), 0null$ifalse))) {
  534.                                 return \constant($const);
  535.                             }
  536.                             throw new ParseException(sprintf('The constant "%s" is not defined.'$const), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  537.                         }
  538.                         if (self::$exceptionOnInvalidType) {
  539.                             throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?'$scalar), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  540.                         }
  541.                         return null;
  542.                     case str_starts_with($scalar'!php/enum'):
  543.                         if (self::$constantSupport) {
  544.                             if (!isset($scalar[11])) {
  545.                                 throw new ParseException('Missing value for tag "!php/enum".'self::$parsedLineNumber 1$scalarself::$parsedFilename);
  546.                             }
  547.                             $i 0;
  548.                             $enum self::parseScalar(substr($scalar10), 0null$ifalse);
  549.                             if ($useValue str_ends_with($enum'->value')) {
  550.                                 $enum substr($enum0, -7);
  551.                             }
  552.                             if (!\defined($enum)) {
  553.                                 throw new ParseException(sprintf('The enum "%s" is not defined.'$enum), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  554.                             }
  555.                             $value \constant($enum);
  556.                             if (!$value instanceof \UnitEnum) {
  557.                                 throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.'$enum), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  558.                             }
  559.                             if (!$useValue) {
  560.                                 return $value;
  561.                             }
  562.                             if (!$value instanceof \BackedEnum) {
  563.                                 throw new ParseException(sprintf('The enum "%s" defines no value next to its name.'$enum), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  564.                             }
  565.                             return $value->value;
  566.                         }
  567.                         if (self::$exceptionOnInvalidType) {
  568.                             throw new ParseException(sprintf('The string "%s" could not be parsed as an enum. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?'$scalar), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  569.                         }
  570.                         return null;
  571.                     case str_starts_with($scalar'!!float '):
  572.                         return (float) substr($scalar8);
  573.                     case str_starts_with($scalar'!!binary '):
  574.                         return self::evaluateBinaryScalar(substr($scalar9));
  575.                 }
  576.                 throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.'$scalar), self::$parsedLineNumber$scalarself::$parsedFilename);
  577.             case preg_match('/^(?:\+|-)?0o(?P<value>[0-7_]++)$/'$scalar$matches):
  578.                 $value str_replace('_'''$matches['value']);
  579.                 if ('-' === $scalar[0]) {
  580.                     return -octdec($value);
  581.                 }
  582.                 return octdec($value);
  583.             case \in_array($scalar[0], ['+''-''.'], true) || is_numeric($scalar[0]):
  584.                 if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}'$scalar)) {
  585.                     $scalar str_replace('_'''$scalar);
  586.                 }
  587.                 switch (true) {
  588.                     case ctype_digit($scalar):
  589.                     case '-' === $scalar[0] && ctype_digit(substr($scalar1)):
  590.                         $cast = (int) $scalar;
  591.                         return ($scalar === (string) $cast) ? $cast $scalar;
  592.                     case is_numeric($scalar):
  593.                     case Parser::preg_match(self::getHexRegex(), $scalar):
  594.                         $scalar str_replace('_'''$scalar);
  595.                         return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
  596.                     case '.inf' === $scalarLower:
  597.                     case '.nan' === $scalarLower:
  598.                         return -log(0);
  599.                     case '-.inf' === $scalarLower:
  600.                         return log(0);
  601.                     case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/'$scalar):
  602.                         return (float) str_replace('_'''$scalar);
  603.                     case Parser::preg_match(self::getTimestampRegex(), $scalar):
  604.                         // When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
  605.                         $time = new \DateTimeImmutable($scalar, new \DateTimeZone('UTC'));
  606.                         if (Yaml::PARSE_DATETIME $flags) {
  607.                             return $time;
  608.                         }
  609.                         try {
  610.                             if (false !== $scalar $time->getTimestamp()) {
  611.                                 return $scalar;
  612.                             }
  613.                         } catch (\ValueError) {
  614.                             // no-op
  615.                         }
  616.                         return $time->format('U');
  617.                 }
  618.         }
  619.         return (string) $scalar;
  620.     }
  621.     private static function parseTag(string $valueint &$iint $flags): ?string
  622.     {
  623.         if ('!' !== $value[$i]) {
  624.             return null;
  625.         }
  626.         $tagLength strcspn($value" \t\n[]{},"$i 1);
  627.         $tag substr($value$i 1$tagLength);
  628.         $nextOffset $i $tagLength 1;
  629.         $nextOffset += strspn($value' '$nextOffset);
  630.         if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']''}'','], true))) {
  631.             throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.'self::$parsedLineNumber 1$valueself::$parsedFilename);
  632.         }
  633.         // Is followed by a scalar and is a built-in tag
  634.         if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[''{'], true)) && ('!' === $tag[0] || \in_array($tag, ['str''php/const''php/enum''php/object'], true))) {
  635.             // Manage in {@link self::evaluateScalar()}
  636.             return null;
  637.         }
  638.         $i $nextOffset;
  639.         // Built-in tags
  640.         if ('' !== $tag && '!' === $tag[0]) {
  641.             throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.'$tag), self::$parsedLineNumber 1$valueself::$parsedFilename);
  642.         }
  643.         if ('' !== $tag && !isset($value[$i])) {
  644.             throw new ParseException(sprintf('Missing value for tag "%s".'$tag), self::$parsedLineNumber 1$valueself::$parsedFilename);
  645.         }
  646.         if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS $flags) {
  647.             return $tag;
  648.         }
  649.         throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".'$tag), self::$parsedLineNumber 1$valueself::$parsedFilename);
  650.     }
  651.     public static function evaluateBinaryScalar(string $scalar): string
  652.     {
  653.         $parsedBinaryData self::parseScalar(preg_replace('/\s/'''$scalar));
  654.         if (!== (\strlen($parsedBinaryData) % 4)) {
  655.             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)), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  656.         }
  657.         if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i'$parsedBinaryData)) {
  658.             throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.'$parsedBinaryData), self::$parsedLineNumber 1$scalarself::$parsedFilename);
  659.         }
  660.         return base64_decode($parsedBinaryDatatrue);
  661.     }
  662.     private static function isBinaryString(string $value): bool
  663.     {
  664.         return !preg_match('//u'$value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/'$value);
  665.     }
  666.     /**
  667.      * Gets a regex that matches a YAML date.
  668.      *
  669.      * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
  670.      */
  671.     private static function getTimestampRegex(): string
  672.     {
  673.         return <<<EOF
  674.         ~^
  675.         (?P<year>[0-9][0-9][0-9][0-9])
  676.         -(?P<month>[0-9][0-9]?)
  677.         -(?P<day>[0-9][0-9]?)
  678.         (?:(?:[Tt]|[ \t]+)
  679.         (?P<hour>[0-9][0-9]?)
  680.         :(?P<minute>[0-9][0-9])
  681.         :(?P<second>[0-9][0-9])
  682.         (?:\.(?P<fraction>[0-9]*))?
  683.         (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
  684.         (?::(?P<tz_minute>[0-9][0-9]))?))?)?
  685.         $~x
  686. EOF;
  687.     }
  688.     /**
  689.      * Gets a regex that matches a YAML number in hexadecimal notation.
  690.      */
  691.     private static function getHexRegex(): string
  692.     {
  693.         return '~^0x[0-9a-f_]++$~i';
  694.     }
  695. }