mirror of
https://github.com/fenom-template/fenom.git
synced 2023-08-10 21:13:07 +03:00
Add macro support
This commit is contained in:
parent
2acb0feff9
commit
2ba3df6536
@ -24,9 +24,9 @@ class Compiler {
|
|||||||
if($name && ($tpl->getStorage()->getOptions() & \Aspect::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
|
if($name && ($tpl->getStorage()->getOptions() & \Aspect::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
|
||||||
$inc = $tpl->getStorage()->compile($name, false);
|
$inc = $tpl->getStorage()->compile($name, false);
|
||||||
$tpl->addDepend($inc);
|
$tpl->addDepend($inc);
|
||||||
return '$_tpl = (array)$tpl; $tpl->exchangeArray('.self::_toArray($p).'+$_tpl); ?>'.$inc->_body.'<?php $tpl->exchangeArray($_tpl); unset($_tpl);';
|
return '$_tpl = (array)$tpl; $tpl->exchangeArray('.self::toArray($p).'+$_tpl); ?>'.$inc->_body.'<?php $tpl->exchangeArray($_tpl); unset($_tpl);';
|
||||||
} else {
|
} else {
|
||||||
return '$tpl->getStorage()->getTemplate('.$cname.')->display('.self::_toArray($p).'+(array)$tpl);';
|
return '$tpl->getStorage()->getTemplate('.$cname.')->display('.self::toArray($p).'+(array)$tpl);';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if($name && ($tpl->getStorage()->getOptions() & \Aspect::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
|
if($name && ($tpl->getStorage()->getOptions() & \Aspect::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
|
||||||
@ -523,7 +523,7 @@ class Compiler {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function stdFuncParser($function, Tokenizer $tokens, Template $tpl) {
|
public static function stdFuncParser($function, Tokenizer $tokens, Template $tpl) {
|
||||||
return "echo $function(".self::_toArray($tpl->parseParams($tokens)).', $tpl);';
|
return "echo $function(".self::toArray($tpl->parseParams($tokens)).', $tpl);';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -564,7 +564,7 @@ class Compiler {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function stdFuncOpen(Tokenizer $tokens, Scope $scope) {
|
public static function stdFuncOpen(Tokenizer $tokens, Scope $scope) {
|
||||||
$scope["params"] = self::_toArray($scope->tpl->parseParams($tokens));
|
$scope["params"] = self::toArray($scope->tpl->parseParams($tokens));
|
||||||
return 'ob_start();';
|
return 'ob_start();';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,7 +580,12 @@ class Compiler {
|
|||||||
return "echo ".$scope["function"].'('.$scope["params"].', ob_get_clean(), $tpl);';
|
return "echo ".$scope["function"].'('.$scope["params"].', ob_get_clean(), $tpl);';
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function _toArray($params) {
|
/**
|
||||||
|
* Convert array of code to string array
|
||||||
|
* @param $params
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function toArray($params) {
|
||||||
$_code = array();
|
$_code = array();
|
||||||
foreach($params as $k => $v) {
|
foreach($params as $k => $v) {
|
||||||
$_code[] = '"'.$k.'" => '.$v;
|
$_code[] = '"'.$k.'" => '.$v;
|
||||||
@ -689,26 +694,45 @@ class Compiler {
|
|||||||
* @param Tokenizer $tokens
|
* @param Tokenizer $tokens
|
||||||
* @param Scope $scope
|
* @param Scope $scope
|
||||||
* @throws ImproperUseException
|
* @throws ImproperUseException
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public static function macroOpen(Tokenizer $tokens, Scope $scope) {
|
public static function macroOpen(Tokenizer $tokens, Scope $scope) {
|
||||||
$tokens->get('.');
|
$scope["name"] = $tokens->get(Tokenizer::MACRO_STRING);
|
||||||
$name = $tokens->get(Tokenizer::MACRO_STRING);
|
$scope["args"] = array();
|
||||||
if($tokens->is('(')) {
|
$scope["defaults"] = array();
|
||||||
$tokens->skip();
|
if(!$tokens->valid()) {
|
||||||
|
return;
|
||||||
return '';
|
|
||||||
} elseif(isset($scope->tpl->_macros[$name])) {
|
|
||||||
$p = $scope->tpl->parseParams($tokens);
|
|
||||||
$scope->closed = true;
|
|
||||||
return '$_tpl = $tpl; $tpl = '.self::_toArray($p).' + '.$scope->tpl->_macros[$name]["defaults"].'; '.$scope->tpl->_macros[$name]["body"].'; $tpl = $_tpl; unset($_tpl);';
|
|
||||||
} else {
|
|
||||||
throw new ImproperUseException("Unknown tag or macros {{$name}}");
|
|
||||||
}
|
}
|
||||||
|
$tokens->next()->need('(')->next();
|
||||||
|
if($tokens->is(')')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||||
|
$scope["args"][] = $param = $tokens->getAndNext();
|
||||||
|
if($tokens->is('=')) {
|
||||||
|
if($tokens->is(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_DNUMBER) || $tokens->isSpecialVal()) {
|
||||||
|
$scope["defaults"][ $param ] = $tokens->current();
|
||||||
|
} else {
|
||||||
|
throw new ImproperUseException("Macro parameters may have only scalar defaults");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tokens->skipIf(',');
|
||||||
|
}
|
||||||
|
$tokens->skipIf(')');
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Tokenizer $tokens
|
||||||
|
* @param Scope $scope
|
||||||
|
*/
|
||||||
public static function macroClose(Tokenizer $tokens, Scope $scope) {
|
public static function macroClose(Tokenizer $tokens, Scope $scope) {
|
||||||
$scope->tpl->_macros[ $scope["name"] ] = $scope->getContent();
|
$scope->tpl->macros[ $scope["name"] ] = array(
|
||||||
|
"body" => $content = $scope->getContent(),
|
||||||
|
"args" => $scope["args"],
|
||||||
|
"defaults" => $scope["defaults"]
|
||||||
|
);
|
||||||
|
$scope->tpl->_body = substr($scope->tpl->_body, 0, strlen($scope->tpl->_body) - strlen($content));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,11 @@ class Template extends Render {
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $_body;
|
public $_body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array of macros
|
||||||
|
*/
|
||||||
|
public $macros = array();
|
||||||
/**
|
/**
|
||||||
* Call stack
|
* Call stack
|
||||||
* @var Scope[]
|
* @var Scope[]
|
||||||
@ -58,11 +63,12 @@ class Template extends Render {
|
|||||||
*/
|
*/
|
||||||
private $_options = 0;
|
private $_options = 0;
|
||||||
|
|
||||||
/** System variables {$smarty.<...>} or {$aspect.<...>}
|
/**
|
||||||
* @var array
|
* Just factory
|
||||||
|
*
|
||||||
|
* @param \Aspect $aspect
|
||||||
|
* @return Template
|
||||||
*/
|
*/
|
||||||
public static $sysvar = array('$aspect' => 1, '$smarty' => 1);
|
|
||||||
|
|
||||||
public static function factory(Aspect $aspect) {
|
public static function factory(Aspect $aspect) {
|
||||||
return new static($aspect);
|
return new static($aspect);
|
||||||
}
|
}
|
||||||
@ -305,6 +311,9 @@ class Template extends Render {
|
|||||||
case '$':
|
case '$':
|
||||||
$code = "echo ".$this->parseExp($tokens).";";
|
$code = "echo ".$this->parseExp($tokens).";";
|
||||||
break;
|
break;
|
||||||
|
case '#':
|
||||||
|
$code = "echo ".$this->parseConst($tokens);
|
||||||
|
break;
|
||||||
case '/':
|
case '/':
|
||||||
$code = $this->_end($tokens);
|
$code = $this->_end($tokens);
|
||||||
break;
|
break;
|
||||||
@ -365,19 +374,24 @@ class Template extends Render {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function _parseAct(Tokenizer $tokens) {
|
private function _parseAct(Tokenizer $tokens) {
|
||||||
|
|
||||||
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
if($tokens->is(Tokenizer::MACRO_STRING)) {
|
||||||
$action = $tokens->current();
|
$action = $tokens->getAndNext();
|
||||||
} else {
|
} else {
|
||||||
return 'echo '.$this->parseExp($tokens).';'; // may be math and boolean expression
|
return 'echo '.$this->parseExp($tokens).';'; // may be math and boolean expression
|
||||||
}
|
}
|
||||||
|
|
||||||
if($tokens->isNext("(", T_NAMESPACE, T_DOUBLE_COLON)) { // just invoke function or static method
|
if($tokens->is("(", T_NAMESPACE, T_DOUBLE_COLON)) { // just invoke function or static method
|
||||||
|
$tokens->back();
|
||||||
return "echo ".$this->parseExp($tokens).";";
|
return "echo ".$this->parseExp($tokens).";";
|
||||||
|
} elseif($tokens->is('.')) {
|
||||||
|
$name = $tokens->skip()->get(Tokenizer::MACRO_STRING);
|
||||||
|
if($action !== "macro") {
|
||||||
|
$name = $action.".".$name;
|
||||||
|
}
|
||||||
|
return $this->parseMacro($tokens, $name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($act = $this->_aspect->getFunction($action)) { // call some function
|
if($act = $this->_aspect->getFunction($action)) { // call some function
|
||||||
$tokens->next();
|
|
||||||
switch($act["type"]) {
|
switch($act["type"]) {
|
||||||
case Aspect::BLOCK_COMPILER:
|
case Aspect::BLOCK_COMPILER:
|
||||||
$scope = new Scope($action, $this, $this->_line, $act, count($this->_stack));
|
$scope = new Scope($action, $this, $this->_line, $act, count($this->_stack));
|
||||||
@ -399,7 +413,6 @@ class Template extends Render {
|
|||||||
|
|
||||||
for($j = $i = count($this->_stack)-1; $i>=0; $i--) { // call function's internal tag
|
for($j = $i = count($this->_stack)-1; $i>=0; $i--) { // call function's internal tag
|
||||||
if($this->_stack[$i]->hasTag($action, $j - $i)) {
|
if($this->_stack[$i]->hasTag($action, $j - $i)) {
|
||||||
$tokens->next();
|
|
||||||
return $this->_stack[$i]->tag($action, $tokens);
|
return $this->_stack[$i]->tag($action, $tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,6 +465,9 @@ class Template extends Render {
|
|||||||
} else {
|
} else {
|
||||||
$term = 1;
|
$term = 1;
|
||||||
}
|
}
|
||||||
|
} elseif(!$term && $tokens->is('#')) {
|
||||||
|
$term = 1;
|
||||||
|
$_exp .= $this->parseConst($tokens);
|
||||||
} elseif(!$term && $tokens->is("(")) {
|
} elseif(!$term && $tokens->is("(")) {
|
||||||
$_exp .= $tokens->getAndNext();
|
$_exp .= $tokens->getAndNext();
|
||||||
$brackets++;
|
$brackets++;
|
||||||
@ -553,11 +569,7 @@ class Template extends Render {
|
|||||||
public function parseVar(Tokenizer $tokens, $deny = 0, &$pure_var = true) {
|
public function parseVar(Tokenizer $tokens, $deny = 0, &$pure_var = true) {
|
||||||
$var = $tokens->get(T_VARIABLE);
|
$var = $tokens->get(T_VARIABLE);
|
||||||
$pure_var = true;
|
$pure_var = true;
|
||||||
if(isset(self::$sysvar[ $var ])) {
|
$_var = '$tpl["'.ltrim($var,'$').'"]';
|
||||||
$_var = $this->_parseSystemVar($tokens);
|
|
||||||
} else {
|
|
||||||
$_var = '$tpl["'.ltrim($var,'$').'"]';
|
|
||||||
}
|
|
||||||
$tokens->next();
|
$tokens->next();
|
||||||
while($t = $tokens->key()) {
|
while($t = $tokens->key()) {
|
||||||
if($t === "." && !($deny & self::DENY_ARRAY)) {
|
if($t === "." && !($deny & self::DENY_ARRAY)) {
|
||||||
@ -846,7 +858,7 @@ class Template extends Render {
|
|||||||
$key = true;
|
$key = true;
|
||||||
$val = false;
|
$val = false;
|
||||||
$_arr .= $tokens->getAndNext().' ';
|
$_arr .= $tokens->getAndNext().' ';
|
||||||
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, T_VARIABLE, T_STRING, T_EMPTY, T_ISSET, "(") && !$val) {
|
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, T_VARIABLE, T_STRING, T_EMPTY, T_ISSET, "(", "#") && !$val) {
|
||||||
$_arr .= $this->parseExp($tokens, true);
|
$_arr .= $this->parseExp($tokens, true);
|
||||||
$key = false;
|
$key = false;
|
||||||
$val = true;
|
$val = true;
|
||||||
@ -874,29 +886,59 @@ class Template extends Render {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse system variable, like $aspect, $smarty
|
* Parse constant
|
||||||
|
* #Ns\MyClass::CONST1, #CONST1, #MyClass::CONST1
|
||||||
*
|
*
|
||||||
* @param Tokenizer $tokens
|
* @param Tokenizer $tokens
|
||||||
* @throws \LogicException
|
* @return string
|
||||||
* @return mixed|string
|
* @throws ImproperUseException
|
||||||
*/
|
*/
|
||||||
private function _parseSystemVar(Tokenizer $tokens) {
|
public function parseConst(Tokenizer $tokens) {
|
||||||
$tokens->getNext(".");
|
$tokens->get('#');
|
||||||
$key = $tokens->getNext(T_STRING, T_CONST);
|
$name = $tokens->getNext(T_STRING);
|
||||||
switch($key) {
|
$tokens->next();
|
||||||
case 'get': return '$_GET';
|
if($tokens->is(T_NAMESPACE)) {
|
||||||
case 'post': return '$_POST';
|
$name .= '\\';
|
||||||
case 'cookies': return '$_COOKIES';
|
$name .= $tokens->getNext(T_STRING);
|
||||||
case 'session': return '$_SESSION';
|
$tokens->next();
|
||||||
case 'request': return '$_REQUEST';
|
}
|
||||||
case 'now': return 'time()';
|
if($tokens->is(T_DOUBLE_COLON)) {
|
||||||
case 'line': return $this->_line;
|
$name .= '::';
|
||||||
case 'tpl_name': return '$tpl->getName()';
|
$name .= $tokens->getNext(T_STRING);
|
||||||
case 'const':
|
$tokens->next();
|
||||||
$tokens->getNext(".");
|
}
|
||||||
return $tokens->getNext(T_STRING);
|
if(defined($name)) {
|
||||||
default:
|
return $name;
|
||||||
throw new \LogicException("Unexpected key '".$tokens->current()."' in system variable");
|
} else {
|
||||||
|
throw new ImproperUseException("Use undefined constant $name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Tokenizer $tokens
|
||||||
|
* @param $name
|
||||||
|
* @return string
|
||||||
|
* @throws ImproperUseException
|
||||||
|
*/
|
||||||
|
public function parseMacro(Tokenizer $tokens, $name) {
|
||||||
|
if(isset($this->macros[ $name ])) {
|
||||||
|
$macro = $this->macros[ $name ];
|
||||||
|
$p = $this->parseParams($tokens);
|
||||||
|
$args = array();
|
||||||
|
foreach($macro["args"] as $arg) {
|
||||||
|
if(isset($p[ $arg ])) {
|
||||||
|
$args[ $arg ] = $p[ $arg ];
|
||||||
|
} elseif(isset($macro["defaults"][ $arg ])) {
|
||||||
|
$args[ $arg ] = $macro["defaults"][ $arg ];
|
||||||
|
} else {
|
||||||
|
throw new ImproperUseException("Macro '$name' require '$arg' argument");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$args = $args ? '$tpl = '.Compiler::toArray($args).';' : '';
|
||||||
|
return '$_tpl = $tpl; '.$args.' ?>'.$macro["body"].'<?php $tpl = $_tpl; unset($_tpl);';
|
||||||
|
} else {
|
||||||
|
var_dump($this->macros);
|
||||||
|
throw new ImproperUseException("Undefined macro '$name'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -983,8 +1025,7 @@ class Template extends Render {
|
|||||||
$tokens->next();
|
$tokens->next();
|
||||||
$params[ $key ] = $this->parseExp($tokens);
|
$params[ $key ] = $this->parseExp($tokens);
|
||||||
} else {
|
} else {
|
||||||
$params[ $key ] = true;
|
$params[ $key ] = 'true';
|
||||||
$params[] = '"'.$key.'"';
|
|
||||||
}
|
}
|
||||||
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_VARIABLE, "[", '(')) {
|
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_VARIABLE, "[", '(')) {
|
||||||
$params[] = $this->parseExp($tokens);
|
$params[] = $this->parseExp($tokens);
|
||||||
|
@ -433,6 +433,19 @@ class Tokenizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip specific token or do nothing
|
||||||
|
*
|
||||||
|
* @param int|string $token1
|
||||||
|
* @return Tokenizer
|
||||||
|
*/
|
||||||
|
public function skipIf($token1/*, $token2, ...*/) {
|
||||||
|
if($this->_valid(func_get_args(), $this->curr[0])) {
|
||||||
|
$this->next();
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check current token's type
|
* Check current token's type
|
||||||
*
|
*
|
||||||
|
18
tests/cases/Aspect/MacrosTest.php
Normal file
18
tests/cases/Aspect/MacrosTest.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
namespace Aspect;
|
||||||
|
|
||||||
|
class MacrosTest extends TestCase {
|
||||||
|
|
||||||
|
public function testMacros() {
|
||||||
|
$tpl = $this->aspect->compileCode('
|
||||||
|
{macro plus(x, y)}
|
||||||
|
x + y = {$x + $y}
|
||||||
|
{/macro}
|
||||||
|
|
||||||
|
Math: {macro.plus x=2 y=3}
|
||||||
|
');
|
||||||
|
|
||||||
|
$this->assertStringStartsWith('x + y = ', trim($tpl->macros["plus"]["body"]));
|
||||||
|
$this->assertSame('Math: x + y = 5', Modifier::strip($tpl->fetch(array()), true));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user