Dev accessor. Add ~~ operator. ++Docs. ++Tests

This commit is contained in:
bzick
2014-10-05 20:37:30 +04:00
parent af7546a8ec
commit e55402c2f4
10 changed files with 436 additions and 274 deletions

View File

@@ -17,7 +17,7 @@ use Fenom\Template;
*/
class Fenom
{
const VERSION = '2.0';
const VERSION = '2.4';
/* Actions */
const INLINE_COMPILER = 1;
const BLOCK_COMPILER = 5;
@@ -81,6 +81,11 @@ class Fenom
*/
public $tag_filters = array();
/**
* @var string[]
*/
public $call_filters = array();
/**
* @var callable[]
*/
@@ -340,6 +345,24 @@ class Fenom
'third' => '!(%s %% 3)'
);
protected $_accessors = array(
'get' => 'Fenom\Accessor::getVar',
'env' => 'Fenom\Accessor::getVar',
'post' => 'Fenom\Accessor::getVar',
'request' => 'Fenom\Accessor::getVar',
'cookie' => 'Fenom\Accessor::getVar',
'globals' => 'Fenom\Accessor::getVar',
'server' => 'Fenom\Accessor::getVar',
'session' => 'Fenom\Accessor::getVar',
'files' => 'Fenom\Accessor::getVar',
'tpl' => 'Fenom\Accessor::tpl',
'version' => 'Fenom\Accessor::version',
'const' => 'Fenom\Accessor::constant',
'php' => 'Fenom\Accessor::php',
'tag' => 'Fenom\Accessor::Tag',
'fetch' => 'Fenom\Accessor::Fetch',
);
/**
* Just factory
*
@@ -510,12 +533,7 @@ class Fenom
* @param array $tags
* @return Fenom
*/
public function addBlockCompiler(
$compiler,
$open_parser,
$close_parser = self::DEFAULT_CLOSE_COMPILER,
array $tags = array()
) {
public function addBlockCompiler($compiler, $open_parser, $close_parser = self::DEFAULT_CLOSE_COMPILER, array $tags = array()) {
$this->_actions[$compiler] = array(
'type' => self::BLOCK_COMPILER,
'open' => $open_parser,
@@ -772,6 +790,53 @@ class Fenom
return $this->_options;
}
/**
* Add global accessor ($.)
* @param string $name
* @param callable $parser
* @return Fenom
*/
public function addAccessor($name, $parser)
{
$this->_accessors[$name] = $parser;
return $this;
}
/**
* Remove accessor
* @param string $name
* @return Fenom
*/
public function removeAccessor($name)
{
unset($this->_accessors[$name]);
return $this;
}
/**
* Get an accessor
* @param string $name
* @return callable
*/
public function getAccessor($name) {
if(isset($this->_accessors[$name])) {
return $this->_accessors[$name];
} else {
return false;
}
}
/**
* Add filter for $.php accessor.
* Uses glob syntax.
* @param string $pattern
* @return $this
*/
public function addCallFilter($pattern) {
$this->call_filters[] = $pattern;
return $this;
}
/**
* @param bool|string $scm
* @return Fenom\ProviderInterface

113
src/Fenom/Accessor.php Normal file
View File

@@ -0,0 +1,113 @@
<?php
/*
* This file is part of Fenom.
*
* (c) 2013 Ivan Shalganov
*
* For the full copyright and license information, please view the license.md
* file that was distributed with this source code.
*/
namespace Fenom;
use Fenom\Error\UnexpectedTokenException;
/**
* Class Accessor
* @package Fenom
*/
class Accessor {
public static $vars = array(
'get' => '$_GET',
'post' => '$_POST',
'session' => '$_SESSION',
'cookie' => '$_COOKIE',
'request' => '$_REQUEST',
'files' => '$_FILES',
'globals' => '$GLOBALS',
'server' => '$_SERVER',
'env' => '$_ENV'
);
/**
* Accessor for global variables
* @param Tokenizer $tokens
* @param Template $tpl
*/
public static function getVar(Tokenizer $tokens, Template $tpl) {
$name = $tokens->prev[Tokenizer::TEXT];
if(isset(self::$vars[$name])) {
$var = $tpl->parseVariable($tokens, self::$vars[$name]);
return "(isset($var) ? $var : null)";
} else {
throw new UnexpectedTokenException($tokens->back());
}
}
/**
* Accessor for template information
* @param Tokenizer $tokens
*/
public static function tpl(Tokenizer $tokens) {
$method = $tokens->skip('.')->need(T_STRING)->getAndNext();
if(method_exists('Fenom\Render', 'get'.$method)) {
return '$tpl->get'.ucfirst($method).'()';
} else {
throw new UnexpectedTokenException($tokens->back());
}
}
public static function version() {
return 'Fenom::VERSION';
}
/**
* @param Tokenizer $tokens
* @return string
*/
public static function constant(Tokenizer $tokens) {
$const = [$tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()];
while($tokens->is('.')) {
$const[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
$const = implode('\\', $const);
if($tokens->is(T_DOUBLE_COLON)) {
$const .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
return '@constant('.var_export($const, true).')';
}
/**
* @param Tokenizer $tokens
* @param Template $tpl
* @return string
*/
public static function php(Tokenizer $tokens, Template $tpl) {
$callable = [$tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()];
while($tokens->is('.')) {
$callable[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
$callable = implode('\\', $callable);
if($tokens->is(T_DOUBLE_COLON)) {
$callable .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
if(!is_callable($callable)) {
throw new \LogicException("PHP method ".str_replace('\\', '.', $callable).' does not exists.');
}
if($tokens->is('(')) {
$arguments = 'array'.$tpl->parseArgs($tokens).'';
} else {
$arguments = 'array()';
}
return 'call_user_func_array('.var_export($callable, true).', '.$arguments.')';
}
public static function tag(Tokenizer $tokens, Template $tpl) {
$tag = $tokens->get(Tokenizer::MACRO_STRING);
$info = $tpl->getStorage()->getTag($tag, $tpl);
if($info['type'] !== \Fenom::INLINE_FUNCTION) {
throw new \LogicException("Only inline functions allowed in accessor");
}
}
}

View File

@@ -90,8 +90,7 @@ class Render extends \ArrayObject
$this->_time = $props["time"];
$this->_depends = $props["depends"];
$this->_macros = $props["macros"];
// $this->_blocks = $props["blocks"];
$this->_code = $code;
$this->_code = $code;
}
/**
@@ -249,19 +248,6 @@ class Render extends \ArrayObject
public function __get($name)
{
if ($name == 'info') {
return array(
'name' => $this->_name,
'schema' => $this->_scm,
'time' => $this->_time
);
} else {
return null;
}
}
public function __isset($name)
{
return $name == 'info';
return $this->$name = null;
}
}

View File

@@ -268,7 +268,7 @@ class Template extends Render
throw new CompileException("Unclosed tag" . (count($_names) > 1 ? "s" : "") . ": " . implode(
", ",
$_names
), 0, 1, $this->_name, $scope->line); // $scope already defined there!
), 0, 1, $this->_name, $scope->line); // for PHPStorm: $scope already defined there!
}
$this->_src = ""; // cleanup
if ($this->_post) {
@@ -662,7 +662,7 @@ class Template extends Render
// parse term
$term = $this->parseTerm($tokens, $var); // term of the expression
if ($term !== false) {
if ($this->_options & Fenom::FORCE_VERIFY) {
if ($var && ($this->_options & Fenom::FORCE_VERIFY)) {
$term = '(isset(' . $term . ') ? ' . $term . ' : null)';
$var = false;
}
@@ -728,6 +728,10 @@ class Template extends Render
if ($tokens->is(T_LNUMBER, T_DNUMBER)) {
$concat[] = "strval(" . $this->parseTerm($tokens) . ")";
} else {
if($tokens->is('~')) {
$tokens->next();
$concat[] = " ";
}
if(!$concat[] = $this->parseTerm($tokens)) {
throw new UnexpectedTokenException($tokens);
}
@@ -788,7 +792,8 @@ class Template extends Render
}
return $code;
} elseif ($tokens->is('$')) {
$var = $this->parseAccessor($tokens, $is_var);
$is_var = false;
$var = $this->parseAccessor($tokens);
return $unary . $var;
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
return $unary . $tokens->getAndNext() . $this->parseVariable($tokens);
@@ -913,44 +918,18 @@ class Template extends Render
/**
* Parse accessor
* @param Tokenizer $tokens
* @return string
*/
public function parseAccessor(Tokenizer $tokens, &$is_var)
public function parseAccessor(Tokenizer $tokens)
{
$is_var = false;
$vars = array(
'get' => '$_GET',
'post' => '$_POST',
'session' => '$_SESSION',
'cookie' => '$_COOKIE',
'request' => '$_REQUEST',
'files' => '$_FILES',
'globals' => '$GLOBALS',
'server' => '$_SERVER',
'env' => '$_ENV',
'tpl' => '$tpl->info'
);
if ($this->_options & Fenom::DENY_ACCESSOR) {
throw new \LogicException("Accessor are disabled");
$accessor = $tokens->need('$')->next()->need('.')->next()->current();
$callback = $this->getStorage()->getAccessor($accessor);
if($callback) {
return call_user_func($callback, $tokens->next(), $this);
} else {
throw new \RuntimeException("Unknown accessor '$accessor'");
}
$key = $tokens->need('$')->next()->need('.')->next()->current();
$tokens->next();
if (isset($vars[$key])) {
$is_var = true;
return $this->parseVariable($tokens, $vars[$key]);
}
switch ($key) {
case 'const':
$tokens->need('.')->next();
$var = '@constant(' . var_export($this->parseName($tokens), true) . ')';
break;
case 'version':
$var = '\Fenom::VERSION';
break;
default:
throw new UnexpectedTokenException($tokens->back());
}
return $var;
}
/**

View File

@@ -11,17 +11,6 @@ namespace Fenom;
use Fenom\Error\UnexpectedTokenException;
/**
* for PHP <5.4 compatible
*/
defined('T_INSTEADOF') || define('T_INSTEADOF', 341);
defined('T_TRAIT') || define('T_TRAIT', 355);
defined('T_TRAIT_C') || define('T_TRAIT_C', 365);
/**
* for PHP <5.5 compatible
*/
defined('T_YIELD') || define('T_YIELD', 267);
/**
* Each token have structure
* - Token (constant T_* or text)
@@ -93,154 +82,62 @@ class Tokenizer
*/
public static $macros = array(
self::MACRO_STRING => array(
\T_ABSTRACT => 1,
\T_ARRAY => 1,
\T_AS => 1,
\T_BREAK => 1,
\T_BREAK => 1,
\T_CASE => 1,
\T_CATCH => 1,
\T_CLASS => 1,
\T_CLASS_C => 1,
\T_CLONE => 1,
\T_CONST => 1,
\T_CONTINUE => 1,
\T_DECLARE => 1,
\T_DEFAULT => 1,
\T_DIR => 1,
\T_DO => 1,
\T_ECHO => 1,
\T_ELSE => 1,
\T_ELSEIF => 1,
\T_EMPTY => 1,
\T_ENDDECLARE => 1,
\T_ENDFOR => 1,
\T_ENDFOREACH => 1,
\T_ENDIF => 1,
\T_ENDSWITCH => 1,
\T_ENDWHILE => 1,
\T_EVAL => 1,
\T_EXIT => 1,
\T_EXTENDS => 1,
\T_FILE => 1,
\T_FINAL => 1,
\T_FOR => 1,
\T_FOREACH => 1,
\T_FUNCTION => 1,
\T_FUNC_C => 1,
\T_GLOBAL => 1,
\T_GOTO => 1,
\T_HALT_COMPILER => 1,
\T_IF => 1,
\T_IMPLEMENTS => 1,
\T_INCLUDE => 1,
\T_INCLUDE_ONCE => 1,
\T_INSTANCEOF => 1,
\T_INSTEADOF => 1,
\T_INTERFACE => 1,
\T_ISSET => 1,
\T_LINE => 1,
\T_LIST => 1,
\T_LOGICAL_AND => 1,
\T_LOGICAL_OR => 1,
\T_LOGICAL_XOR => 1,
\T_METHOD_C => 1,
\T_NAMESPACE => 1,
\T_NS_C => 1,
\T_NEW => 1,
\T_PRINT => 1,
\T_PRIVATE => 1,
\T_PUBLIC => 1,
\T_PROTECTED => 1,
\T_REQUIRE => 1,
\T_REQUIRE_ONCE => 1,
\T_RETURN => 1,
\T_RETURN => 1,
\T_STRING => 1,
\T_SWITCH => 1,
\T_THROW => 1,
\T_TRAIT => 1,
\T_TRAIT_C => 1,
\T_TRY => 1,
\T_UNSET => 1,
\T_USE => 1,
\T_VAR => 1,
\T_WHILE => 1,
\T_YIELD => 1
\T_ABSTRACT => 1, \T_ARRAY => 1, \T_AS => 1, \T_BREAK => 1,
\T_BREAK => 1, \T_CASE => 1, \T_CATCH => 1, \T_CLASS => 1,
\T_CLASS_C => 1, \T_CLONE => 1, \T_CONST => 1, \T_CONTINUE => 1,
\T_DECLARE => 1, \T_DEFAULT => 1, \T_DIR => 1, \T_DO => 1,
\T_ECHO => 1, \T_ELSE => 1, \T_ELSEIF => 1, \T_EMPTY => 1,
\T_ENDDECLARE => 1, \T_ENDFOR => 1, \T_ENDFOREACH => 1, \T_ENDIF => 1,
\T_ENDSWITCH => 1, \T_ENDWHILE => 1, \T_EVAL => 1, \T_EXIT => 1,
\T_EXTENDS => 1, \T_FILE => 1, \T_FINAL => 1, \T_FOR => 1,
\T_FOREACH => 1, \T_FUNCTION => 1, \T_FUNC_C => 1, \T_GLOBAL => 1,
\T_GOTO => 1, \T_HALT_COMPILER => 1, \T_IF => 1, \T_IMPLEMENTS => 1,
\T_INCLUDE => 1, \T_INCLUDE_ONCE => 1, \T_INSTANCEOF => 1, 341 /* T_INSTEADOF */ => 1,
\T_INTERFACE => 1, \T_ISSET => 1, \T_LINE => 1, \T_LIST => 1,
\T_LOGICAL_AND => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_METHOD_C => 1,
\T_NAMESPACE => 1, \T_NS_C => 1, \T_NEW => 1, \T_PRINT => 1,
\T_PRIVATE => 1, \T_PUBLIC => 1, \T_PROTECTED => 1, \T_REQUIRE => 1,
\T_REQUIRE_ONCE => 1, \T_RETURN => 1, \T_RETURN => 1, \T_STRING => 1,
\T_SWITCH => 1, \T_THROW => 1, 355 /* T_TRAIT */ => 1, 365 /* T_TRAIT_C */ => 1,
\T_TRY => 1, \T_UNSET => 1, \T_USE => 1, \T_VAR => 1,
\T_WHILE => 1, 267 /* T_YIELD */ => 1
),
self::MACRO_INCDEC => array(
\T_INC => 1,
\T_DEC => 1
\T_INC => 1, \T_DEC => 1
),
self::MACRO_UNARY => array(
"!" => 1,
"~" => 1,
"-" => 1
"!" => 1, "~" => 1, "-" => 1
),
self::MACRO_BINARY => array(
\T_BOOLEAN_AND => 1,
\T_BOOLEAN_OR => 1,
\T_IS_GREATER_OR_EQUAL => 1,
\T_IS_EQUAL => 1,
\T_IS_IDENTICAL => 1,
\T_IS_NOT_EQUAL => 1,
\T_IS_NOT_IDENTICAL => 1,
\T_IS_SMALLER_OR_EQUAL => 1,
\T_LOGICAL_AND => 1,
\T_LOGICAL_OR => 1,
\T_LOGICAL_XOR => 1,
\T_SL => 1,
\T_SR => 1,
"+" => 1,
"-" => 1,
"*" => 1,
"/" => 1,
">" => 1,
"<" => 1,
"^" => 1,
"%" => 1,
\T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_IS_GREATER_OR_EQUAL => 1,
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, \T_IS_NOT_EQUAL => 1,
\T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1, \T_LOGICAL_AND => 1,
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_SL => 1,
\T_SR => 1, "+" => 1, "-" => 1,
"*" => 1, "/" => 1, ">" => 1,
"<" => 1, "^" => 1, "%" => 1,
"&" => 1
),
self::MACRO_BOOLEAN => array(
\T_LOGICAL_OR => 1,
\T_LOGICAL_XOR => 1,
\T_BOOLEAN_AND => 1,
\T_BOOLEAN_OR => 1,
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1,
\T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1,
\T_LOGICAL_AND => 1
),
self::MACRO_MATH => array(
"+" => 1,
"-" => 1,
"*" => 1,
"/" => 1,
"^" => 1,
"%" => 1,
"&" => 1,
"|" => 1
"+" => 1, "-" => 1, "*" => 1,
"/" => 1, "^" => 1, "%" => 1,
"&" => 1, "|" => 1
),
self::MACRO_COND => array(
\T_IS_EQUAL => 1,
\T_IS_IDENTICAL => 1,
">" => 1,
"<" => 1,
\T_SL => 1,
\T_SR => 1,
\T_IS_NOT_EQUAL => 1,
\T_IS_NOT_IDENTICAL => 1,
\T_IS_SMALLER_OR_EQUAL => 1,
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1,
"<" => 1, \T_SL => 1, \T_SR => 1,
\T_IS_NOT_EQUAL => 1, \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1,
),
self::MACRO_EQUALS => array(
\T_AND_EQUAL => 1,
\T_DIV_EQUAL => 1,
\T_MINUS_EQUAL => 1,
\T_MOD_EQUAL => 1,
\T_MUL_EQUAL => 1,
\T_OR_EQUAL => 1,
\T_PLUS_EQUAL => 1,
\T_SL_EQUAL => 1,
\T_SR_EQUAL => 1,
\T_XOR_EQUAL => 1,
'=' => 1,
\T_AND_EQUAL => 1, \T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1,
\T_MOD_EQUAL => 1, \T_MUL_EQUAL => 1, \T_OR_EQUAL => 1,
\T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
\T_XOR_EQUAL => 1, '=' => 1,
),
self::MACRO_SCALAR => array(
\T_LNUMBER => 1,