1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* PHPTAL templating engine |
4
|
|
|
* |
5
|
|
|
* PHP Version 5 |
6
|
|
|
* |
7
|
|
|
* @category HTML |
8
|
|
|
* @package PHPTAL |
9
|
|
|
* @author Laurent Bedubourg <[email protected]> |
10
|
|
|
* @author Kornel Lesiński <[email protected]> |
11
|
|
|
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License |
12
|
|
|
* @version SVN: $Id$ |
13
|
|
|
* @link http://phptal.org/ |
14
|
|
|
*/ |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Tranform php: expressions into their php equivalent. |
18
|
|
|
* |
19
|
|
|
* This transformer produce php code for expressions like : |
20
|
|
|
* |
21
|
|
|
* - a.b["key"].c().someVar[10].foo() |
22
|
|
|
* - (a or b) and (c or d) |
23
|
|
|
* - not myBool |
24
|
|
|
* - ... |
25
|
|
|
* |
26
|
|
|
* The $prefix variable may be changed to change the context lookup. |
27
|
|
|
* |
28
|
|
|
* example: |
29
|
|
|
* |
30
|
|
|
* $res = PHPTAL_Php_Transformer::transform('a.b.c[x]', '$ctx->'); |
31
|
|
|
* $res == '$ctx->a->b->c[$ctx->x]'; |
32
|
|
|
* |
33
|
|
|
* @package PHPTAL |
34
|
|
|
* @subpackage Php |
35
|
|
|
* @author Laurent Bedubourg <[email protected]> |
36
|
|
|
*/ |
37
|
|
|
class PHPTAL_Php_Transformer |
38
|
|
|
{ |
39
|
|
|
const ST_WHITE = -1; // start of string or whitespace |
40
|
|
|
const ST_NONE = 0; // pass through (operators, parens, etc.) |
41
|
|
|
const ST_STR = 1; // 'foo' |
42
|
|
|
const ST_ESTR = 2; // "foo ${x} bar" |
43
|
|
|
const ST_VAR = 3; // abcd |
44
|
|
|
const ST_NUM = 4; // 123.02 |
45
|
|
|
const ST_EVAL = 5; // $somevar |
46
|
|
|
const ST_MEMBER = 6; // abcd.x |
47
|
|
|
const ST_STATIC = 7; // class::[$]static|const |
48
|
|
|
const ST_DEFINE = 8; // @MY_DEFINE |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* transform PHPTAL's php-like syntax into real PHP |
52
|
|
|
*/ |
53
|
|
|
public static function transform($str, $prefix='$') |
54
|
|
|
{ |
55
|
|
|
$len = strlen($str); |
56
|
|
|
$state = self::ST_WHITE; |
57
|
|
|
$result = ''; |
58
|
|
|
$i = 0; |
|
|
|
|
59
|
|
|
$inString = false; |
|
|
|
|
60
|
|
|
$backslashed = false; |
61
|
|
|
$instanceof = false; |
62
|
|
|
$eval = false; |
63
|
|
|
|
64
|
|
|
|
65
|
|
|
for ($i = 0; $i <= $len; $i++) { |
66
|
|
|
if ($i == $len) $c = "\0"; |
67
|
|
|
else $c = $str[$i]; |
68
|
|
|
|
69
|
|
|
switch ($state) { |
70
|
|
|
|
71
|
|
|
// after whitespace a variable-variable may start, ${var} → $ctx->{$ctx->var} |
72
|
|
|
case self::ST_WHITE: |
73
|
|
|
if ($c === '$' && $i+1 < $len && $str[$i+1] === '{') |
74
|
|
|
{ |
75
|
|
|
$result .= $prefix; |
76
|
|
|
$state = self::ST_NONE; |
77
|
|
|
break; |
78
|
|
|
} |
79
|
|
|
/* NO BREAK - ST_WHITE is almost the same as ST_NONE */ |
80
|
|
|
|
81
|
|
|
// no specific state defined, just eat char and see what to do with it. |
82
|
|
|
case self::ST_NONE: |
83
|
|
|
// begin of eval without { |
84
|
|
|
if ($c === '$' && $i+1 < $len && self::isAlpha($str[$i+1])) { |
85
|
|
|
$state = self::ST_EVAL; |
86
|
|
|
$mark = $i+1; |
87
|
|
|
$result .= $prefix.'{'; |
88
|
|
|
} |
89
|
|
|
elseif (self::isDigit($c)) |
90
|
|
|
{ |
91
|
|
|
$state = self::ST_NUM; |
92
|
|
|
$mark = $i; |
93
|
|
|
} |
94
|
|
|
// that an alphabetic char, then it should be the begining |
95
|
|
|
// of a var or static |
96
|
|
|
// && !self::isDigit($c) checked earlier |
97
|
|
|
elseif (self::isVarNameChar($c)) { |
98
|
|
|
$state = self::ST_VAR; |
99
|
|
|
$mark = $i; |
100
|
|
|
} |
101
|
|
|
// begining of double quoted string |
102
|
|
|
elseif ($c === '"') { |
103
|
|
|
$state = self::ST_ESTR; |
104
|
|
|
$mark = $i; |
105
|
|
|
$inString = true; |
|
|
|
|
106
|
|
|
} |
107
|
|
|
// begining of single quoted string |
108
|
|
|
elseif ($c === '\'') { |
109
|
|
|
$state = self::ST_STR; |
110
|
|
|
$mark = $i; |
111
|
|
|
$inString = true; |
|
|
|
|
112
|
|
|
} |
113
|
|
|
// closing a method, an array access or an evaluation |
114
|
|
|
elseif ($c === ')' || $c === ']' || $c === '}') { |
115
|
|
|
$result .= $c; |
116
|
|
|
// if next char is dot then an object member must |
117
|
|
|
// follow |
118
|
|
|
if ($i+1 < $len && $str[$i+1] === '.') { |
119
|
|
|
$result .= '->'; |
120
|
|
|
$state = self::ST_MEMBER; |
121
|
|
|
$mark = $i+2; |
122
|
|
|
$i+=2; |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
// @ is an access to some defined variable |
126
|
|
|
elseif ($c === '@') { |
127
|
|
|
$state = self::ST_DEFINE; |
128
|
|
|
$mark = $i+1; |
129
|
|
|
} |
130
|
|
|
elseif (ctype_space($c)) { |
131
|
|
|
$state = self::ST_WHITE; |
132
|
|
|
$result .= $c; |
133
|
|
|
} |
134
|
|
|
// character we don't mind about |
135
|
|
|
else { |
136
|
|
|
$result .= $c; |
137
|
|
|
} |
138
|
|
|
break; |
139
|
|
|
|
140
|
|
|
// $xxx |
141
|
|
View Code Duplication |
case self::ST_EVAL: |
|
|
|
|
142
|
|
|
if (!self::isVarNameChar($c)) { |
143
|
|
|
$result .= $prefix . substr($str, $mark, $i-$mark); |
|
|
|
|
144
|
|
|
$result .= '}'; |
145
|
|
|
$state = self::ST_NONE; |
146
|
|
|
} |
147
|
|
|
break; |
148
|
|
|
|
149
|
|
|
// single quoted string |
150
|
|
|
case self::ST_STR: |
151
|
|
|
if ($c === '\\') { |
152
|
|
|
$backslashed = true; |
153
|
|
|
} elseif ($backslashed) { |
154
|
|
|
$backslashed = false; |
155
|
|
|
} |
156
|
|
|
// end of string, back to none state |
157
|
|
|
elseif ($c === '\'') { |
158
|
|
|
$result .= substr($str, $mark, $i-$mark+1); |
159
|
|
|
$inString = false; |
|
|
|
|
160
|
|
|
$state = self::ST_NONE; |
161
|
|
|
} |
162
|
|
|
break; |
163
|
|
|
|
164
|
|
|
// double quoted string |
165
|
|
|
case self::ST_ESTR: |
166
|
|
|
if ($c === '\\') { |
167
|
|
|
$backslashed = true; |
168
|
|
|
} elseif ($backslashed) { |
169
|
|
|
$backslashed = false; |
170
|
|
|
} |
171
|
|
|
// end of string, back to none state |
172
|
|
|
elseif ($c === '"') { |
173
|
|
|
$result .= substr($str, $mark, $i-$mark+1); |
174
|
|
|
$inString = false; |
|
|
|
|
175
|
|
|
$state = self::ST_NONE; |
176
|
|
|
} |
177
|
|
|
// instring interpolation, search } and transform the |
178
|
|
|
// interpollation to insert it into the string |
179
|
|
|
elseif ($c === '$' && $i+1 < $len && $str[$i+1] === '{') { |
180
|
|
|
$result .= substr($str, $mark, $i-$mark) . '{'; |
181
|
|
|
|
182
|
|
|
$sub = 0; |
183
|
|
|
for ($j = $i; $j<$len; $j++) { |
184
|
|
|
if ($str[$j] === '{') { |
185
|
|
|
$sub++; |
186
|
|
|
} elseif ($str[$j] === '}' && (--$sub) == 0) { |
187
|
|
|
$part = substr($str, $i+2, $j-$i-2); |
188
|
|
|
$result .= self::transform($part, $prefix); |
189
|
|
|
$i = $j; |
190
|
|
|
$mark = $i; |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
break; |
195
|
|
|
|
196
|
|
|
// var state |
197
|
|
|
case self::ST_VAR: |
198
|
|
|
if (self::isVarNameChar($c)) { |
199
|
|
|
} |
200
|
|
|
// end of var, begin of member (method or var) |
201
|
|
|
elseif ($c === '.') { |
202
|
|
|
$result .= $prefix . substr($str, $mark, $i-$mark); |
203
|
|
|
$result .= '->'; |
204
|
|
|
$state = self::ST_MEMBER; |
205
|
|
|
$mark = $i+1; |
206
|
|
|
} |
207
|
|
|
// static call, the var is a class name |
208
|
|
|
elseif ($c === ':' && $i+1 < $len && $str[$i+1] === ':') { |
209
|
|
|
$result .= substr($str, $mark, $i-$mark+1); |
210
|
|
|
$mark = $i+1; |
211
|
|
|
$i++; |
212
|
|
|
$state = self::ST_STATIC; |
213
|
|
|
break; |
214
|
|
|
} |
215
|
|
|
// function invocation, the var is a function name |
216
|
|
|
elseif ($c === '(') { |
217
|
|
|
$result .= substr($str, $mark, $i-$mark+1); |
218
|
|
|
$state = self::ST_NONE; |
219
|
|
|
} |
220
|
|
|
// array index, the var is done |
221
|
|
|
elseif ($c === '[') { |
222
|
|
|
if ($str[$mark]==='_') { // superglobal? |
223
|
|
|
$result .= '$' . substr($str, $mark, $i-$mark+1); |
224
|
|
|
} else { |
225
|
|
|
$result .= $prefix . substr($str, $mark, $i-$mark+1); |
226
|
|
|
} |
227
|
|
|
$state = self::ST_NONE; |
228
|
|
|
} |
229
|
|
|
// end of var with non-var-name character, handle keywords |
230
|
|
|
// and populate the var name |
231
|
|
|
else { |
232
|
|
|
$var = substr($str, $mark, $i-$mark); |
233
|
|
|
$low = strtolower($var); |
234
|
|
|
// boolean and null |
235
|
|
|
if ($low === 'true' || $low === 'false' || $low === 'null') { |
236
|
|
|
$result .= $var; |
237
|
|
|
} |
238
|
|
|
// lt, gt, ge, eq, ... |
239
|
|
|
elseif (array_key_exists($low, self::$TranslationTable)) { |
240
|
|
|
$result .= self::$TranslationTable[$low]; |
241
|
|
|
} |
242
|
|
|
// instanceof keyword |
243
|
|
|
elseif ($low === 'instanceof') { |
244
|
|
|
$result .= $var; |
245
|
|
|
$instanceof = true; |
246
|
|
|
} |
247
|
|
|
// previous was instanceof |
248
|
|
|
elseif ($instanceof) { |
249
|
|
|
// last was instanceof, this var is a class name |
250
|
|
|
$result .= $var; |
251
|
|
|
$instanceof = false; |
252
|
|
|
} |
253
|
|
|
// regular variable |
254
|
|
|
else { |
255
|
|
|
$result .= $prefix . $var; |
256
|
|
|
} |
257
|
|
|
$i--; |
258
|
|
|
$state = self::ST_NONE; |
259
|
|
|
} |
260
|
|
|
break; |
261
|
|
|
|
262
|
|
|
// object member |
263
|
|
|
case self::ST_MEMBER: |
264
|
|
|
if (self::isVarNameChar($c)) { |
265
|
|
|
} |
266
|
|
|
// eval mode ${foo} |
267
|
|
|
elseif ($c === '$' && ($i >= $len-2 || $str[$i+1] !== '{')) { |
268
|
|
|
$result .= '{' . $prefix; |
269
|
|
|
$mark++; |
270
|
|
|
$eval = true; |
271
|
|
|
} |
272
|
|
|
// x.${foo} x->{foo} |
273
|
|
|
elseif ($c === '$') { |
274
|
|
|
$mark++; |
275
|
|
|
} |
276
|
|
|
// end of var member var, begin of new member |
277
|
|
View Code Duplication |
elseif ($c === '.') { |
|
|
|
|
278
|
|
|
$result .= substr($str, $mark, $i-$mark); |
279
|
|
|
if ($eval) { $result .='}'; $eval = false; } |
280
|
|
|
$result .= '->'; |
281
|
|
|
$mark = $i+1; |
282
|
|
|
$state = self::ST_MEMBER; |
283
|
|
|
} |
284
|
|
|
// begin of static access |
285
|
|
|
elseif ($c === ':') { |
286
|
|
|
$result .= substr($str, $mark, $i-$mark+1); |
287
|
|
|
if ($eval) { $result .='}'; $eval = false; } |
288
|
|
|
$state = self::ST_STATIC; |
289
|
|
|
break; |
290
|
|
|
} |
291
|
|
|
// the member is a method or an array |
292
|
|
View Code Duplication |
elseif ($c === '(' || $c === '[') { |
|
|
|
|
293
|
|
|
$result .= substr($str, $mark, $i-$mark+1); |
294
|
|
|
if ($eval) { $result .='}'; $eval = false; } |
295
|
|
|
$state = self::ST_NONE; |
296
|
|
|
} |
297
|
|
|
// regular end of member, it is a var |
298
|
|
|
else { |
299
|
|
|
$var = substr($str, $mark, $i-$mark); |
300
|
|
|
if ($var !== '' && !preg_match('/^[a-z][a-z0-9_\x7f-\xff]*$/i',$var)) { |
301
|
|
|
throw new PHPTAL_ParserException("Invalid field name '$var' in expression php:$str"); |
302
|
|
|
} |
303
|
|
|
$result .= $var; |
304
|
|
|
if ($eval) { $result .='}'; $eval = false; } |
305
|
|
|
$state = self::ST_NONE; |
306
|
|
|
$i--; |
307
|
|
|
} |
308
|
|
|
break; |
309
|
|
|
|
310
|
|
|
// wait for separator |
311
|
|
View Code Duplication |
case self::ST_DEFINE: |
|
|
|
|
312
|
|
|
if (self::isVarNameChar($c)) { |
313
|
|
|
} else { |
314
|
|
|
$state = self::ST_NONE; |
315
|
|
|
$result .= substr($str, $mark, $i-$mark); |
316
|
|
|
$i--; |
317
|
|
|
} |
318
|
|
|
break; |
319
|
|
|
|
320
|
|
|
// static call, can be const, static var, static method |
321
|
|
|
// Klass::$static |
322
|
|
|
// Klass::const |
323
|
|
|
// Kclass::staticMethod() |
324
|
|
|
// |
325
|
|
|
case self::ST_STATIC: |
326
|
|
|
if (self::isVarNameChar($c)) { |
327
|
|
|
} |
328
|
|
|
// static var |
329
|
|
|
elseif ($c === '$') { |
330
|
|
|
} |
331
|
|
|
// end of static var which is an object and begin of member |
332
|
|
|
elseif ($c === '.') { |
333
|
|
|
$result .= substr($str, $mark, $i-$mark); |
334
|
|
|
$result .= '->'; |
335
|
|
|
$mark = $i+1; |
336
|
|
|
$state = self::ST_MEMBER; |
337
|
|
|
} |
338
|
|
|
// end of static var which is a class name |
339
|
|
|
elseif ($c === ':') { |
340
|
|
|
$result .= substr($str, $mark, $i-$mark+1); |
341
|
|
|
$state = self::ST_STATIC; |
342
|
|
|
break; |
343
|
|
|
} |
344
|
|
|
// static method or array |
345
|
|
|
elseif ($c === '(' || $c === '[') { |
346
|
|
|
$result .= substr($str, $mark, $i-$mark+1); |
347
|
|
|
$state = self::ST_NONE; |
348
|
|
|
} |
349
|
|
|
// end of static var or const |
350
|
|
|
else { |
351
|
|
|
$result .= substr($str, $mark, $i-$mark); |
352
|
|
|
$state = self::ST_NONE; |
353
|
|
|
$i--; |
354
|
|
|
} |
355
|
|
|
break; |
356
|
|
|
|
357
|
|
|
// numeric value |
358
|
|
|
case self::ST_NUM: |
359
|
|
|
if (!self::isDigitCompound($c)) { |
360
|
|
|
$var = substr($str, $mark, $i-$mark); |
361
|
|
|
|
362
|
|
|
if (self::isAlpha($c) || $c === '_') { |
363
|
|
|
throw new PHPTAL_ParserException("Syntax error in number '$var$c' in expression php:$str"); |
364
|
|
|
} |
365
|
|
|
if (!is_numeric($var)) { |
366
|
|
|
throw new PHPTAL_ParserException("Syntax error in number '$var' in expression php:$str"); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
$result .= $var; |
370
|
|
|
$state = self::ST_NONE; |
371
|
|
|
$i--; |
372
|
|
|
} |
373
|
|
|
break; |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
$result = trim($result); |
378
|
|
|
|
379
|
|
|
// CodeWriter doesn't like expressions that look like blocks |
380
|
|
|
if ($result[strlen($result)-1] === '}') return '('.$result.')'; |
381
|
|
|
|
382
|
|
|
return $result; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
private static function isAlpha($c) |
386
|
|
|
{ |
387
|
|
|
$c = strtolower($c); |
388
|
|
|
return $c >= 'a' && $c <= 'z'; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
private static function isDigit($c) |
392
|
|
|
{ |
393
|
|
|
return ($c >= '0' && $c <= '9'); |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
private static function isDigitCompound($c) |
397
|
|
|
{ |
398
|
|
|
return ($c >= '0' && $c <= '9' || $c === '.'); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
private static function isVarNameChar($c) |
402
|
|
|
{ |
403
|
|
|
return self::isAlpha($c) || ($c >= '0' && $c <= '9') || $c === '_' || $c === '\\'; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
private static $TranslationTable = array( |
407
|
|
|
'not' => '!', |
408
|
|
|
'ne' => '!=', |
409
|
|
|
'and' => '&&', |
410
|
|
|
'or' => '||', |
411
|
|
|
'lt' => '<', |
412
|
|
|
'gt' => '>', |
413
|
|
|
'ge' => '>=', |
414
|
|
|
'le' => '<=', |
415
|
|
|
'eq' => '==', |
416
|
|
|
); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
|
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.