// /** * 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.php * @author Laurent Bedubourg */ class PHPTAL_Php_Transformer { const ST_NONE = 0; 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 public static function transform( $str, $prefix='$' ) { // // Here comes the good old state machine. // TODO: benchmark this version and then benchmark a refactored version // with states behaviour separated into methods, keep the fastest. // $len = strlen($str); $state = self::ST_NONE; $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) { // no 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 else if (self::isAlpha($c) || $c==='_') { $state = self::ST_VAR; $mark = $i; } // begining of double quoted string else if ($c == '"') { $state = self::ST_ESTR; $mark = $i; $inString = true; } // begining of single quoted string else if ($c == '\'') { $state = self::ST_STR; $mark = $i; $inString = true; } // closing a method, an array access or an evaluation else if ($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 else if ($c == '@') { $state = self::ST_DEFINE; $mark = $i+1; } // 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; } else if ($backslashed) { $backslashed = false; } // end of string, back to none state else if ($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; } else if ($backslashed) { $backslashed = false; } // end of string, back to none state else if ($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 else if ($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) else if ($c == '.') { $result .= $prefix . substr( $str, $mark, $i-$mark ); $result .= '->'; $state = self::ST_MEMBER; $mark = $i+1; } // static call, the var is a class name else if ($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 else if ($c == '(') { $result .= substr( $str, $mark, $i-$mark+1 ); $state = self::ST_NONE; } // array index, the var is done else if ($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, ... else if (array_key_exists($low, self::$TranslationTable)){ $result .= self::$TranslationTable[$low]; } // instanceof keyword else if ($low == 'instanceof'){ $result .= $var; $instanceOf = true; } // previous was instanceof else if ($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} else if ($c == '$') { $result .= '{' . $prefix; $mark++; $eval = true; } // end of var member var, begin of new member else if ($c == '.') { $result .= substr( $str, $mark, $i-$mark ); if ($eval) { $result .='}'; $eval = false; } $result .= '->'; $mark = $i+1; $state = self::ST_MEMBER; } // begin of static access else if ($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 else if ($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 else if ($c == '$') { } // end of static var which is an object and begin of member else if ($c == '.') { $result .= substr( $str, $mark, $i-$mark ); $result .= '->'; $mark = $i+1; $state = self::ST_MEMBER; } // end of static var which is a class name else if ($c == ':') { $result .= substr( $str, $mark, $i-$mark+1 ); $state = self::ST_STATIC; break; } // static method or array else if ($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; } } return trim($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' => '==', ); } ?>