1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of Railt package. |
4
|
|
|
* |
5
|
|
|
* For the full copyright and license information, please view the LICENSE |
6
|
|
|
* file that was distributed with this source code. |
7
|
|
|
*/ |
8
|
|
|
declare(strict_types=1); |
9
|
|
|
|
10
|
|
|
namespace Railt\SDL\Frontend\Builder\Value; |
11
|
|
|
|
12
|
|
|
use Railt\Parser\Ast\RuleInterface; |
13
|
|
|
use Railt\SDL\Frontend\Builder\BaseBuilder; |
14
|
|
|
use Railt\SDL\Frontend\Context\ContextInterface; |
15
|
|
|
use Railt\SDL\IR\SymbolTable\Value; |
16
|
|
|
use Railt\SDL\IR\SymbolTable\ValueInterface; |
17
|
|
|
use Railt\SDL\IR\Type; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Class StringValueBuilder |
21
|
|
|
*/ |
22
|
|
|
class StringValueBuilder extends BaseBuilder |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* @var string |
26
|
|
|
*/ |
27
|
|
|
private const UTF_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\u([0-9a-f]{4})/ui'; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var string |
31
|
|
|
*/ |
32
|
|
|
private const CHAR_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\(b|f|n|r|t)/u'; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param RuleInterface $rule |
36
|
|
|
* @return bool |
37
|
|
|
*/ |
38
|
|
|
public function match(RuleInterface $rule): bool |
39
|
|
|
{ |
40
|
|
|
return $rule->getName() === 'StringValue'; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @param ContextInterface $ctx |
45
|
|
|
* @param RuleInterface $rule |
46
|
|
|
* @return mixed|Value |
47
|
|
|
*/ |
48
|
|
|
public function reduce(ContextInterface $ctx, RuleInterface $rule): ValueInterface |
49
|
|
|
{ |
50
|
|
|
$value = $this->parse($this->getNativeValue($rule)); |
51
|
|
|
|
52
|
|
|
return new Value($value, Type::string()); |
|
|
|
|
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @param string $value |
57
|
|
|
* @return string |
58
|
|
|
*/ |
59
|
|
|
public function parse(string $value): string |
60
|
|
|
{ |
61
|
|
|
// Encode slashes to special "pattern" chars |
62
|
|
|
$value = $this->encodeSlashes($value); |
63
|
|
|
|
64
|
|
|
// Transform utf char \uXXXX -> X |
65
|
|
|
$value = $this->renderUtfSequences($value); |
66
|
|
|
|
67
|
|
|
// Transform special chars |
68
|
|
|
$value = $this->renderSpecialCharacters($value); |
69
|
|
|
|
70
|
|
|
// Decode special patterns to source chars (rollback) |
71
|
|
|
$value = $this->decodeSlashes($value); |
72
|
|
|
|
73
|
|
|
return $value; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @param string $value |
78
|
|
|
* @return string |
79
|
|
|
*/ |
80
|
|
|
private function encodeSlashes(string $value): string |
81
|
|
|
{ |
82
|
|
|
return \str_replace(['\\\\', '\\"'], ["\0", '"'], $value); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Method for parsing and decode utf-8 character |
87
|
|
|
* sequences like "\uXXXX" type. |
88
|
|
|
* |
89
|
|
|
* @see http://facebook.github.io/graphql/October2016/#sec-String-Value |
90
|
|
|
* @param string $body |
91
|
|
|
* @return string |
92
|
|
|
*/ |
93
|
|
|
private function renderUtfSequences(string $body): string |
94
|
|
|
{ |
95
|
|
|
$callee = function (array $matches): string { |
96
|
|
|
[$char, $code] = [$matches[0], $matches[1]]; |
|
|
|
|
97
|
|
|
|
98
|
|
|
try { |
99
|
|
|
return $this->forwardRenderUtfSequences($code); |
100
|
|
|
} catch (\Error | \ErrorException $error) { |
|
|
|
|
101
|
|
|
return $this->fallbackRenderUtfSequences($char); |
102
|
|
|
} |
103
|
|
|
}; |
104
|
|
|
|
105
|
|
|
return @\preg_replace_callback(self::UTF_SEQUENCE_PATTERN, $callee, $body) ?? $body; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @param string $body |
110
|
|
|
* @return string |
111
|
|
|
*/ |
112
|
|
|
private function forwardRenderUtfSequences(string $body): string |
113
|
|
|
{ |
114
|
|
|
return \mb_convert_encoding(\pack('H*', $body), 'UTF-8', 'UCS-2BE'); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @param string $body |
119
|
|
|
* @return string |
120
|
|
|
*/ |
121
|
|
|
private function fallbackRenderUtfSequences(string $body): string |
122
|
|
|
{ |
123
|
|
|
try { |
124
|
|
|
if (\function_exists('\\json_decode')) { |
125
|
|
|
$result = @\json_decode('{"char": "' . $body . '"}')->char; |
126
|
|
|
|
127
|
|
|
if (\json_last_error() === \JSON_ERROR_NONE) { |
128
|
|
|
$body = $result; |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
} finally { |
132
|
|
|
return $body; |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Method for parsing special control characters. |
138
|
|
|
* |
139
|
|
|
* @see http://facebook.github.io/graphql/October2016/#sec-String-Value |
140
|
|
|
* @param string $body |
141
|
|
|
* @return string |
142
|
|
|
*/ |
143
|
|
|
private function renderSpecialCharacters(string $body): string |
144
|
|
|
{ |
145
|
|
|
$callee = function (array $matches): string { |
146
|
|
|
[$char, $code] = [$matches[0], $matches[1]]; |
|
|
|
|
147
|
|
|
|
148
|
|
|
switch ($code) { |
149
|
|
|
case 'b': |
150
|
|
|
return "\u{0008}"; |
151
|
|
|
case 'f': |
152
|
|
|
return "\u{000C}"; |
153
|
|
|
case 'n': |
154
|
|
|
return "\u{000A}"; |
155
|
|
|
case 'r': |
156
|
|
|
return "\u{000D}"; |
157
|
|
|
case 't': |
158
|
|
|
return "\u{0009}"; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
return $char; |
162
|
|
|
}; |
163
|
|
|
|
164
|
|
|
return @\preg_replace_callback(self::CHAR_SEQUENCE_PATTERN, $callee, $body) ?? $body; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @param string $value |
169
|
|
|
* @return string |
170
|
|
|
*/ |
171
|
|
|
private function decodeSlashes(string $value): string |
172
|
|
|
{ |
173
|
|
|
return \str_replace("\0", '\\', $value); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @param RuleInterface $rule |
178
|
|
|
* @return string |
179
|
|
|
*/ |
180
|
|
|
private function getNativeValue(RuleInterface $rule): string |
181
|
|
|
{ |
182
|
|
|
return $rule->getChild(0)->getValue(1); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* @return string |
187
|
|
|
*/ |
188
|
|
|
private function getStringValue(): string |
|
|
|
|
189
|
|
|
{ |
190
|
|
|
/** @var LeafInterface $leaf */ |
191
|
|
|
$leaf = $this->getChild(0); |
|
|
|
|
192
|
|
|
|
193
|
|
|
return $leaf->getValue(1); |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.