1<?php declare(strict_types=1);
2
3namespace PhpParser\Node\Scalar;
4
5use PhpParser\Error;
6use PhpParser\Node\Scalar;
7
8class String_ extends Scalar
9{
10    /* For use in "kind" attribute */
11    const KIND_SINGLE_QUOTED = 1;
12    const KIND_DOUBLE_QUOTED = 2;
13    const KIND_HEREDOC = 3;
14    const KIND_NOWDOC = 4;
15
16    /** @var string String value */
17    public $value;
18
19    protected static $replacements = [
20        '\\' => '\\',
21        '$'  =>  '$',
22        'n'  => "\n",
23        'r'  => "\r",
24        't'  => "\t",
25        'f'  => "\f",
26        'v'  => "\v",
27        'e'  => "\x1B",
28    ];
29
30    /**
31     * Constructs a string scalar node.
32     *
33     * @param string $value      Value of the string
34     * @param array  $attributes Additional attributes
35     */
36    public function __construct(string $value, array $attributes = []) {
37        $this->attributes = $attributes;
38        $this->value = $value;
39    }
40
41    public function getSubNodeNames() : array {
42        return ['value'];
43    }
44
45    /**
46     * @internal
47     *
48     * Parses a string token.
49     *
50     * @param string $str String token content
51     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
52     *
53     * @return string The parsed string
54     */
55    public static function parse(string $str, bool $parseUnicodeEscape = true) : string {
56        $bLength = 0;
57        if ('b' === $str[0] || 'B' === $str[0]) {
58            $bLength = 1;
59        }
60
61        if ('\'' === $str[$bLength]) {
62            return str_replace(
63                ['\\\\', '\\\''],
64                ['\\', '\''],
65                substr($str, $bLength + 1, -1)
66            );
67        } else {
68            return self::parseEscapeSequences(
69                substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape
70            );
71        }
72    }
73
74    /**
75     * @internal
76     *
77     * Parses escape sequences in strings (all string types apart from single quoted).
78     *
79     * @param string      $str   String without quotes
80     * @param null|string $quote Quote type
81     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
82     *
83     * @return string String with escape sequences parsed
84     */
85    public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = true) : string {
86        if (null !== $quote) {
87            $str = str_replace('\\' . $quote, $quote, $str);
88        }
89
90        $extra = '';
91        if ($parseUnicodeEscape) {
92            $extra = '|u\{([0-9a-fA-F]+)\}';
93        }
94
95        return preg_replace_callback(
96            '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~',
97            function($matches) {
98                $str = $matches[1];
99
100                if (isset(self::$replacements[$str])) {
101                    return self::$replacements[$str];
102                } elseif ('x' === $str[0] || 'X' === $str[0]) {
103                    return chr(hexdec(substr($str, 1)));
104                } elseif ('u' === $str[0]) {
105                    return self::codePointToUtf8(hexdec($matches[2]));
106                } else {
107                    return chr(octdec($str));
108                }
109            },
110            $str
111        );
112    }
113
114    /**
115     * Converts a Unicode code point to its UTF-8 encoded representation.
116     *
117     * @param int $num Code point
118     *
119     * @return string UTF-8 representation of code point
120     */
121    private static function codePointToUtf8(int $num) : string {
122        if ($num <= 0x7F) {
123            return chr($num);
124        }
125        if ($num <= 0x7FF) {
126            return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80);
127        }
128        if ($num <= 0xFFFF) {
129            return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
130        }
131        if ($num <= 0x1FFFFF) {
132            return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80)
133                 . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
134        }
135        throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
136    }
137
138    public function getType() : string {
139        return 'Scalar_String';
140    }
141}
142