* @author Kornel Lesiński
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
* @version SVN: $Id: Transformer.php 605 2009-05-03 02:50:26Z kornel $
* @link http://phptal.org/
*/
/**
* Tranform php: expressions into their php equivalent.
*
* This transformer produce php code for expressions like :
*
* - a.b["key"].c().someVar[10].foo()
* - (a or b) and (c or d)
* - not myBool
* - ...
*
* The $prefix variable may be changed to change the context lookup.
*
* example:
*
* $res = PHPTAL_Php_Transformer::transform('a.b.c[x]', '$ctx->');
* $res == '$ctx->a->b->c[$ctx->x]';
*
* @package PHPTAL
* @subpackage Php
* @author Laurent Bedubourg
*/
class PHPTAL_Php_Transformer
{
const ST_WHITE = -1; // start of string or whitespace
const ST_NONE = 0; // pass through (operators, parens, etc.)
const ST_STR = 1; // 'foo'
const ST_ESTR = 2; // "foo ${x} bar"
const ST_VAR = 3; // abcd
const ST_NUM = 4; // 123.02
const ST_EVAL = 5; // $somevar
const ST_MEMBER = 6; // abcd.x
const ST_STATIC = 7; // class::[$]static|const
const ST_DEFINE = 8; // @MY_DEFINE
/**
* transform PHPTAL's php-like syntax into real PHP
*/
public static function transform($str, $prefix='$')
{
$len = strlen($str);
$state = self::ST_WHITE;
$result = '';
$i = 0;
$inString = false;
$backslashed = false;
$instanceOf = false;
$eval = false;
for ($i = 0; $i <= $len; $i++) {
if ($i == $len) $c = "\0";
else $c = $str[$i];
switch ($state) {
// after whitespace a variable-variable may start, ${var} → $ctx->{$ctx->var}
case self::ST_WHITE:
if ($c === '$' && $i < $len-2 && $str[$i+1] === '{')
{
$result .= $prefix;
$state = self::ST_NONE;
continue;
}
/* NO BREAK - ST_WHITE is almost the same as ST_NONE */
// no specific state defined, just eat char and see what to do with it.
case self::ST_NONE:
// begin of eval without {
if ($c === '$' && $i < $len && self::isAlpha($str[$i+1])) {
$state = self::ST_EVAL;
$mark = $i+1;
$result .= $prefix.'{';
}
// that an alphabetic char, then it should be the begining
// of a var
elseif (self::isAlpha($c) || $c==='_') {
$state = self::ST_VAR;
$mark = $i;
}
// begining of double quoted string
elseif ($c === '"') {
$state = self::ST_ESTR;
$mark = $i;
$inString = true;
}
// begining of single quoted string
elseif ($c === '\'') {
$state = self::ST_STR;
$mark = $i;
$inString = true;
}
// closing a method, an array access or an evaluation
elseif ($c === ')' || $c === ']' || $c === '}') {
$result .= $c;
// if next char is dot then an object member must
// follow
if ($i < $len-1 && $str[$i+1] === '.') {
$result .= '->';
$state = self::ST_MEMBER;
$mark = $i+2;
$i+=2;
}
}
// @ is an access to some defined variable
elseif ($c === '@') {
$state = self::ST_DEFINE;
$mark = $i+1;
}
elseif (ctype_space($c)) {
$state = self::ST_WHITE;
$result .= $c;
}
// character we don't mind about
else {
$result .= $c;
}
break;
// $xxx
case self::ST_EVAL:
if (!self::isVarNameChar($c)) {
$result .= $prefix . substr($str, $mark, $i-$mark);
$result .= '}';
$state = self::ST_NONE;
}
break;
// single quoted string
case self::ST_STR:
if ($c === '\\') {
$backslashed = true;
} elseif ($backslashed) {
$backslashed = false;
}
// end of string, back to none state
elseif ($c === '\'') {
$result .= substr($str, $mark, $i-$mark+1);
$inString = false;
$state = self::ST_NONE;
}
break;
// double quoted string
case self::ST_ESTR:
if ($c === '\\') {
$backslashed = true;
} elseif ($backslashed) {
$backslashed = false;
}
// end of string, back to none state
elseif ($c === '"') {
$result .= substr($str, $mark, $i-$mark+1);
$inString = false;
$state = self::ST_NONE;
}
// instring interpolation, search } and transform the
// interpollation to insert it into the string
elseif ($c === '$' && $i < $len && $str[$i+1] === '{') {
$result .= substr($str, $mark, $i-$mark) . '{';
$sub = 0;
for ($j = $i; $j<$len; $j++) {
if ($str[$j] === '{') {
$sub++;
} elseif ($str[$j] === '}' && (--$sub) == 0) {
$part = substr($str, $i+2, $j-$i-2);
$result .= self::transform($part, $prefix);
$i = $j;
$mark = $i;
}
}
}
break;
// var state
case self::ST_VAR:
if (self::isVarNameChar($c)) {
}
// end of var, begin of member (method or var)
elseif ($c === '.') {
$result .= $prefix . substr($str, $mark, $i-$mark);
$result .= '->';
$state = self::ST_MEMBER;
$mark = $i+1;
}
// static call, the var is a class name
elseif ($c === ':') {
$result .= substr($str, $mark, $i-$mark+1);
$mark = $i+1;
$i++;
$state = self::ST_STATIC;
break;
}
// function invocation, the var is a function name
elseif ($c === '(') {
$result .= substr($str, $mark, $i-$mark+1);
$state = self::ST_NONE;
}
// array index, the var is done
elseif ($c === '[') {
if ($str[$mark]==='_') { // superglobal?
$result .= '$' . substr($str, $mark, $i-$mark+1);
} else {
$result .= $prefix . substr($str, $mark, $i-$mark+1);
}
$state = self::ST_NONE;
}
// end of var with non-var-name character, handle keywords
// and populate the var name
else {
$var = substr($str, $mark, $i-$mark);
$low = strtolower($var);
// boolean and null
if ($low === 'true' || $low === 'false' || $low === 'null') {
$result .= $var;
}
// lt, gt, ge, eq, ...
elseif (array_key_exists($low, self::$TranslationTable)) {
$result .= self::$TranslationTable[$low];
}
// instanceof keyword
elseif ($low === 'instanceof') {
$result .= $var;
$instanceOf = true;
}
// previous was instanceof
elseif ($instanceOf) {
// last was instanceof, this var is a class name
$result .= $var;
$instanceOf = false;
}
// regular variable
else {
$result .= $prefix . $var;
}
$i--;
$state = self::ST_NONE;
}
break;
// object member
case self::ST_MEMBER:
if (self::isVarNameChar($c)) {
}
// eval mode ${foo}
elseif ($c === '$' && ($i >= $len-2 || $str[$i+1] !== '{')) {
$result .= '{' . $prefix;
$mark++;
$eval = true;
}
// x.${foo} x->{foo}
elseif ($c === '$') {
$mark++;
}
// end of var member var, begin of new member
elseif ($c === '.') {
$result .= substr($str, $mark, $i-$mark);
if ($eval) { $result .='}'; $eval = false; }
$result .= '->';
$mark = $i+1;
$state = self::ST_MEMBER;
}
// begin of static access
elseif ($c === ':') {
$result .= substr($str, $mark, $i-$mark+1);
if ($eval) { $result .='}'; $eval = false; }
$state = self::ST_STATIC;
break;
}
// the member is a method or an array
elseif ($c === '(' || $c === '[') {
$result .= substr($str, $mark, $i-$mark+1);
if ($eval) { $result .='}'; $eval = false; }
$state = self::ST_NONE;
}
// regular end of member, it is a var
else {
$result .= substr($str, $mark, $i-$mark);
if ($eval) { $result .='}'; $eval = false; }
$state = self::ST_NONE;
$i--;
}
break;
// wait for separator
case self::ST_DEFINE:
if (self::isVarNameChar($c)) {
} else {
$state = self::ST_NONE;
$result .= substr($str, $mark, $i-$mark);
$i--;
}
break;
// static call, can be const, static var, static method
// Klass::$static
// Klass::const
// Kclass::staticMethod()
//
case self::ST_STATIC:
if (self::isVarNameChar($c)) {
}
// static var
elseif ($c === '$') {
}
// end of static var which is an object and begin of member
elseif ($c === '.') {
$result .= substr($str, $mark, $i-$mark);
$result .= '->';
$mark = $i+1;
$state = self::ST_MEMBER;
}
// end of static var which is a class name
elseif ($c === ':') {
$result .= substr($str, $mark, $i-$mark+1);
$state = self::ST_STATIC;
break;
}
// static method or array
elseif ($c === '(' || $c === '[') {
$result .= substr($str, $mark, $i-$mark+1);
$state = self::ST_NONE;
}
// end of static var or const
else {
$result .= substr($str, $mark, $i-$mark);
$state = self::ST_NONE;
$i--;
}
break;
// numeric value
case self::ST_NUM:
if (!self::isDigitCompound($c)) {
$result .= substr($str, $mark, $i-$mark);
$state = self::ST_NONE;
}
break;
}
}
$result = trim($result);
// CodeWriter doesn't like expressions that look like blocks
if ($result[strlen($result)-1] === '}') return '('.$result.')';
return $result;
}
private static function isAlpha($c)
{
$c = strtolower($c);
return $c >= 'a' && $c <= 'z';
}
private static function isDigitCompound($c)
{
return ($c >= '0' && $c <= '9' || $c === '.');
}
private static function isVarNameChar($c)
{
return self::isAlpha($c) || ($c >= '0' && $c <= '9') || $c === '_';
}
private static $TranslationTable = array(
'not' => '!',
'ne' => '!=',
'and' => '&&',
'or' => '||',
'lt' => '<',
'gt' => '>',
'ge' => '>=',
'le' => '<=',
'eq' => '==',
);
}