diff options
Diffstat (limited to 'lib/phptal/PHPTAL/Php/Transformer.php')
-rw-r--r-- | lib/phptal/PHPTAL/Php/Transformer.php | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/lib/phptal/PHPTAL/Php/Transformer.php b/lib/phptal/PHPTAL/Php/Transformer.php new file mode 100644 index 0000000..c07608d --- /dev/null +++ b/lib/phptal/PHPTAL/Php/Transformer.php @@ -0,0 +1,418 @@ +<?php +/** + * PHPTAL templating engine + * + * PHP Version 5 + * + * @category HTML + * @package PHPTAL + * @author Laurent Bedubourg <lbedubourg@motion-twin.com> + * @author Kornel Lesiński <kornel@aardvarkmedia.co.uk> + * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License + * @version SVN: $Id$ + * @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 <lbedubourg@motion-twin.com> + */ +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+1 < $len && $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+1 < $len && self::isAlpha($str[$i+1])) { + $state = self::ST_EVAL; + $mark = $i+1; + $result .= $prefix.'{'; + } + elseif (self::isDigit($c)) + { + $state = self::ST_NUM; + $mark = $i; + } + // that an alphabetic char, then it should be the begining + // of a var or static + // && !self::isDigit($c) checked earlier + elseif (self::isVarNameChar($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+1 < $len && $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+1 < $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 === ':' && $i+1 < $len && $str[$i+1] === ':') { + $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 { + $var = substr($str, $mark, $i-$mark); + if ($var !== '' && !preg_match('/^[a-z][a-z0-9_\x7f-\xff]*$/i',$var)) { + throw new PHPTAL_ParserException("Invalid field name '$var' in expression php:$str"); + } + $result .= $var; + 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)) { + $var = substr($str, $mark, $i-$mark); + + if (self::isAlpha($c) || $c === '_') { + throw new PHPTAL_ParserException("Syntax error in number '$var$c' in expression php:$str"); + } + if (!is_numeric($var)) { + throw new PHPTAL_ParserException("Syntax error in number '$var' in expression php:$str"); + } + + $result .= $var; + $state = self::ST_NONE; + $i--; + } + 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 isDigit($c) + { + return ($c >= '0' && $c <= '9'); + } + + 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 === '_' || $c === '\\'; + } + + private static $TranslationTable = array( + 'not' => '!', + 'ne' => '!=', + 'and' => '&&', + 'or' => '||', + 'lt' => '<', + 'gt' => '>', + 'ge' => '>=', + 'le' => '<=', + 'eq' => '==', + ); +} + |