|   1:  | <?php declare(strict_types = 1); | 
|   2:  |  | 
|   3:  | namespace PHPStan\PhpDocParser\Parser; | 
|   4:  |  | 
|   5:  | use PHPStan\ShouldNotHappenException; | 
|   6:  | use function chr; | 
|   7:  | use function hexdec; | 
|   8:  | use function octdec; | 
|   9:  | use function preg_replace_callback; | 
|  10:  | use function str_replace; | 
|  11:  | use function substr; | 
|  12:  |  | 
|  13:  | class StringUnescaper | 
|  14:  | { | 
|  15:  |  | 
|  16:  | 	private const REPLACEMENTS = [ | 
|  17:  | 		'\\' => '\\', | 
|  18:  | 		'n' => "\n", | 
|  19:  | 		'r' => "\r", | 
|  20:  | 		't' => "\t", | 
|  21:  | 		'f' => "\f", | 
|  22:  | 		'v' => "\v", | 
|  23:  | 		'e' => "\x1B", | 
|  24:  | 	]; | 
|  25:  |  | 
|  26:  | 	public static function unescapeString(string $string): string | 
|  27:  | 	{ | 
|  28:  | 		$quote = $string[0]; | 
|  29:  |  | 
|  30:  | 		if ($quote === '\'') { | 
|  31:  | 			return str_replace( | 
|  32:  | 				['\\\\', '\\\''], | 
|  33:  | 				['\\', '\''], | 
|  34:  | 				substr($string, 1, -1), | 
|  35:  | 			); | 
|  36:  | 		} | 
|  37:  |  | 
|  38:  | 		return self::parseEscapeSequences(substr($string, 1, -1), '"'); | 
|  39:  | 	} | 
|  40:  |  | 
|  41:  | 	 | 
|  42:  |  | 
|  43:  |  | 
|  44:  | 	private static function parseEscapeSequences(string $str, string $quote): string | 
|  45:  | 	{ | 
|  46:  | 		$str = str_replace('\\' . $quote, $quote, $str); | 
|  47:  |  | 
|  48:  | 		return preg_replace_callback( | 
|  49:  | 			'~\\\\([\\\\nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u\{([0-9a-fA-F]+)\})~', | 
|  50:  | 			static function ($matches) { | 
|  51:  | 				$str = $matches[1]; | 
|  52:  |  | 
|  53:  | 				if (isset(self::REPLACEMENTS[$str])) { | 
|  54:  | 					return self::REPLACEMENTS[$str]; | 
|  55:  | 				} | 
|  56:  | 				if ($str[0] === 'x' || $str[0] === 'X') { | 
|  57:  | 					return chr((int) hexdec(substr($str, 1))); | 
|  58:  | 				} | 
|  59:  | 				if ($str[0] === 'u') { | 
|  60:  | 					if (!isset($matches[2])) { | 
|  61:  | 						throw new ShouldNotHappenException(); | 
|  62:  | 					} | 
|  63:  | 					return self::codePointToUtf8((int) hexdec($matches[2])); | 
|  64:  | 				} | 
|  65:  |  | 
|  66:  | 				return chr((int) octdec($str)); | 
|  67:  | 			}, | 
|  68:  | 			$str, | 
|  69:  | 		); | 
|  70:  | 	} | 
|  71:  |  | 
|  72:  | 	 | 
|  73:  |  | 
|  74:  |  | 
|  75:  | 	private static function codePointToUtf8(int $num): string | 
|  76:  | 	{ | 
|  77:  | 		if ($num <= 0x7F) { | 
|  78:  | 			return chr($num); | 
|  79:  | 		} | 
|  80:  | 		if ($num <= 0x7FF) { | 
|  81:  | 			return chr(($num >> 6) + 0xC0) | 
|  82:  | 				. chr(($num & 0x3F) + 0x80); | 
|  83:  | 		} | 
|  84:  | 		if ($num <= 0xFFFF) { | 
|  85:  | 			return chr(($num >> 12) + 0xE0) | 
|  86:  | 				. chr((($num >> 6) & 0x3F) + 0x80) | 
|  87:  | 				. chr(($num & 0x3F) + 0x80); | 
|  88:  | 		} | 
|  89:  | 		if ($num <= 0x1FFFFF) { | 
|  90:  | 			return chr(($num >> 18) + 0xF0) | 
|  91:  | 				. chr((($num >> 12) & 0x3F) + 0x80) | 
|  92:  | 				. chr((($num >> 6) & 0x3F) + 0x80) | 
|  93:  | 				. chr(($num & 0x3F) + 0x80); | 
|  94:  | 		} | 
|  95:  |  | 
|  96:  | 		 | 
|  97:  | 		return "\xef\xbf\xbd"; | 
|  98:  | 	} | 
|  99:  |  | 
| 100:  | } | 
| 101:  |  |