1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Jade\Compiler; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Class Jade\Compiler\CodeHandler. |
7
|
|
|
*/ |
8
|
|
|
class CodeHandler extends CompilerUtils |
9
|
|
|
{ |
10
|
|
|
protected $input; |
11
|
|
|
protected $name; |
12
|
|
|
protected $separators; |
13
|
|
|
|
14
|
|
|
public function __construct($input, $name) |
15
|
|
|
{ |
16
|
|
|
if (!is_string($input)) { |
17
|
|
|
throw new \InvalidArgumentException('Expecting a string of PHP, got: ' . gettype($input), 11); |
18
|
|
|
} |
19
|
|
|
|
20
|
|
|
if (strlen($input) === 0) { |
21
|
|
|
throw new \InvalidArgumentException('Expecting a string of PHP, empty string received.', 12); |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
$this->input = trim(preg_replace('/\bvar\b/', '', $input)); |
25
|
|
|
$this->name = $name; |
26
|
|
|
$this->separators = array(); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
public function innerCode($input, $name) |
30
|
|
|
{ |
31
|
|
|
$handler = new static($input, $name); |
32
|
|
|
|
33
|
|
|
return $handler->parse(); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
public function parse() |
37
|
|
|
{ |
38
|
|
|
if ($this->isQuotedString()) { |
39
|
|
|
return array($this->input); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
if (strpos('=,;?', substr($this->input, 0, 1)) !== false) { |
43
|
|
|
throw new \ErrorException('Expecting a variable name or an expression, got: ' . $this->input, 13); |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
preg_match_all( |
47
|
|
|
'/(?<![<>=!])=(?!>|=)|[\[\]\{\}\(\),;\.]|(?!:):|->/', // punctuation |
48
|
|
|
preg_replace_callback('/[a-zA-Z0-9\\\\_\\x7f-\\xff]*\((?:[0-9\/%\.\s*+-]++|(?R))*+\)/', function ($match) { |
49
|
|
|
// no need to keep separators in simple PHP expressions (functions calls, parentheses, calculs) |
50
|
|
|
return str_repeat(' ', strlen($match[0])); |
51
|
|
|
}, preg_replace_callback('/([\'"]).*?(?<!\\\\)(?:\\\\{2})*\\1/', function ($match) { |
52
|
|
|
// do not take separators in strings |
53
|
|
|
return str_repeat(' ', strlen($match[0])); |
54
|
|
|
}, $this->input)), |
55
|
|
|
$separators, |
56
|
|
|
PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE |
57
|
|
|
); |
58
|
|
|
|
59
|
|
|
$this->separators = $separators[0]; |
60
|
|
|
|
61
|
|
|
if (count($this->separators) === 0) { |
62
|
|
|
if (strstr('0123456789-+("\'$', substr($this->input, 0, 1)) === false) { |
63
|
|
|
$this->input = static::addDollarIfNeeded($this->input); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
return array($this->input); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
// add a pseudo separator for the end of the input |
70
|
|
|
array_push($this->separators, array(null, strlen($this->input))); |
71
|
|
|
|
72
|
|
|
return $this->parseBetweenSeparators(); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
protected function isQuotedString() |
76
|
|
|
{ |
77
|
|
|
$firstChar = substr($this->input, 0, 1); |
78
|
|
|
$lastChar = substr($this->input, -1); |
79
|
|
|
|
80
|
|
|
return false !== strpos('"\'', $firstChar) && $lastChar === $firstChar; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
protected function getVarname($separator) |
84
|
|
|
{ |
85
|
|
|
// do not add $ if it is not like a variable |
86
|
|
|
$varname = static::convertVarPath(substr($this->input, 0, $separator[1]), '/^%s/'); |
87
|
|
|
|
88
|
|
|
return $separator[0] !== '(' && $varname !== '' && strstr('0123456789-+("\'$', substr($varname, 0, 1)) === false |
89
|
|
|
? static::addDollarIfNeeded($varname) |
90
|
|
|
: $varname; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
protected function parseArrayString(&$argument, $match, $consume, &$quote, &$key, &$value) |
94
|
|
|
{ |
95
|
|
|
$quote = $quote |
96
|
|
|
? CommonUtils::escapedEnd($match[1]) |
97
|
|
|
? $quote |
98
|
|
|
: null |
99
|
|
|
: $match[2]; |
100
|
|
|
${is_null($value) ? 'key' : 'value'} .= $match[0]; |
101
|
|
|
$consume($argument, $match[0]); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
protected function parseArrayAssign(&$argument, $match, $consume, &$quote, &$key, &$value) |
105
|
|
|
{ |
106
|
|
|
if ($quote) { |
107
|
|
|
${is_null($value) ? 'key' : 'value'} .= $match[0]; |
108
|
|
|
$consume($argument, $match[0]); |
109
|
|
|
|
110
|
|
|
return; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
if (!is_null($value)) { |
114
|
|
|
throw new \ErrorException('Parse error on ' . substr($argument, strlen($match[1])), 15); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
$key .= $match[1]; |
118
|
|
|
$value = ''; |
119
|
|
|
$consume($argument, $match[0]); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
protected function parseArrayElement(&$argument, $match, $consume, &$quote, &$key, &$value) |
123
|
|
|
{ |
124
|
|
|
switch ($match[2]) { |
125
|
|
|
case '"': |
126
|
|
|
case "'": |
127
|
|
|
$this->parseArrayString($argument, $match, $consume, $quote, $key, $value); |
128
|
|
|
break; |
129
|
|
|
case ':': |
130
|
|
|
case '=>': |
131
|
|
|
$this->parseArrayAssign($argument, $match, $consume, $quote, $key, $value); |
132
|
|
|
break; |
133
|
|
|
case ',': |
134
|
|
|
${is_null($value) ? 'key' : 'value'} .= $match[0]; |
135
|
|
|
$consume($argument, $match[0]); |
136
|
|
|
break; |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
protected function parseArray($input, $subCodeHandler) |
141
|
|
|
{ |
142
|
|
|
$output = array(); |
143
|
|
|
$key = ''; |
144
|
|
|
$value = null; |
145
|
|
|
$addToOutput = $subCodeHandler->addToOutput($output, $key, $value); |
146
|
|
|
$consume = $subCodeHandler->consume(); |
147
|
|
|
foreach ($input as $argument) { |
148
|
|
|
$argument = ltrim($argument, '$'); |
149
|
|
|
$quote = null; |
150
|
|
|
while (preg_match('/^(.*?)(=>|[\'",:])/', $argument, $match)) { |
151
|
|
|
$this->parseArrayElement($argument, $match, $consume, $quote, $key, $value); |
152
|
|
|
} |
153
|
|
|
${is_null($value) ? 'key' : 'value'} .= $argument; |
154
|
|
|
$addToOutput(); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
return 'array(' . implode(', ', $output) . ')'; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
protected function parseEqual($sep, &$separators, &$result, $innerName, $subCodeHandler) |
161
|
|
|
{ |
162
|
|
|
if (preg_match('/^[[:space:]]*$/', $innerName)) { |
163
|
|
|
next($separators); |
164
|
|
|
$handleCodeInbetween = $subCodeHandler->handleCodeInbetween($separators, $result); |
165
|
|
|
|
166
|
|
|
return implode($handleCodeInbetween()); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
$handleRecursion = $subCodeHandler->handleRecursion($result); |
170
|
|
|
|
171
|
|
|
return $handleRecursion(array($sep, end($separators))); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
protected function parseSeparator($sep, &$separators, &$result, &$varname, $subCodeHandler, $innerName) |
175
|
|
|
{ |
176
|
|
|
$handleCodeInbetween = $subCodeHandler->handleCodeInbetween($separators, $result); |
177
|
|
|
$var = '$__' . $this->name; |
178
|
|
|
|
179
|
|
|
switch ($sep[0]) { |
180
|
|
|
// translate the javascript's obj.attr into php's obj->attr or obj['attr'] |
181
|
|
|
/* |
|
|
|
|
182
|
|
|
case '.': |
183
|
|
|
$result[] = sprintf("%s=is_array(%s)?%s['%s']:%s->%s", |
184
|
|
|
$var, $varname, $varname, $innerName, $varname, $innerName |
185
|
|
|
); |
186
|
|
|
$varname = $var; |
187
|
|
|
break; |
188
|
|
|
//*/ |
189
|
|
|
|
190
|
|
|
// funcall |
191
|
|
|
case '(': |
192
|
|
|
$arguments = $handleCodeInbetween(); |
193
|
|
|
$call = $varname . '(' . implode(', ', $arguments) . ')'; |
194
|
|
|
$call = static::addDollarIfNeeded($call); |
195
|
|
|
$varname = $var; |
196
|
|
|
array_push($result, "{$var}={$call}"); |
197
|
|
|
break; |
198
|
|
|
|
199
|
|
|
case '[': |
|
|
|
|
200
|
|
|
if (preg_match('/[a-zA-Z0-9\\\\_\\x7f-\\xff]$/', $varname)) { |
201
|
|
|
$varname .= $sep[0] . $innerName; |
202
|
|
|
break; |
203
|
|
|
} |
204
|
|
|
case '{': |
205
|
|
|
$varname .= $this->parseArray($handleCodeInbetween(), $subCodeHandler); |
206
|
|
|
break; |
207
|
|
|
|
208
|
|
|
case '=': |
209
|
|
|
$varname .= '=' . $this->parseEqual($sep, $separators, $result, $innerName, $subCodeHandler); |
210
|
|
|
break; |
211
|
|
|
|
212
|
|
|
default: |
213
|
|
|
if (($innerName !== false && $innerName !== '') || $sep[0] !== ')') { |
214
|
|
|
$varname .= $sep[0] . $innerName; |
215
|
|
|
} |
216
|
|
|
break; |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
protected function parseBetweenSeparators() |
221
|
|
|
{ |
222
|
|
|
$separators = $this->separators; |
223
|
|
|
|
224
|
|
|
$result = array(); |
225
|
|
|
|
226
|
|
|
$varname = $this->getVarname($separators[0]); |
227
|
|
|
|
228
|
|
|
$subCodeHandler = new SubCodeHandler($this, $this->input, $this->name); |
229
|
|
|
$getMiddleString = $subCodeHandler->getMiddleString(); |
230
|
|
|
$getNext = $subCodeHandler->getNext($separators); |
231
|
|
|
|
232
|
|
|
// using next() ourselves so that we can advance the array pointer inside inner loops |
233
|
|
|
while (($sep = current($separators)) && $sep[0] !== null) { |
234
|
|
|
// $sep[0] - the separator string due to PREG_SPLIT_OFFSET_CAPTURE flag or null if end of string |
235
|
|
|
// $sep[1] - the offset due to PREG_SPLIT_OFFSET_CAPTURE |
236
|
|
|
|
237
|
|
|
$innerName = $getMiddleString($sep, $getNext(key($separators))); |
238
|
|
|
|
239
|
|
|
$this->parseSeparator($sep, $separators, $result, $varname, $subCodeHandler, $innerName); |
240
|
|
|
|
241
|
|
|
next($separators); |
242
|
|
|
} |
243
|
|
|
array_push($result, $varname); |
244
|
|
|
|
245
|
|
|
return $result; |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.