From 7ae08e3c35760fd7c5cb00d9a8a27488090f952d Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Wed, 23 Oct 2013 15:22:12 +0200 Subject: Added some tests. Many of these break. Mostly because the way whitespace/newlines are added (or not added). This is terribly broken ATM... (It was okay with the old parser, I'll try to replicate that behaviour) --- tests/test_short/want | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/test_short/want (limited to 'tests/test_short/want') diff --git a/tests/test_short/want b/tests/test_short/want new file mode 100644 index 0000000..a0aba93 --- /dev/null +++ b/tests/test_short/want @@ -0,0 +1 @@ +OK \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 6dc747c097e096075b4726eb4aebf1ce0ed71276 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Fri, 25 Oct 2013 21:10:32 +0200 Subject: short cmp tags can now contain tags. To accomplish this, tag parameters can now be compiled, even if they contain tags (the parser still doesn't allow this, but the compiler can now handle this situation). Also reformatted the code. --- docu/language_definition.html | 5 +- stupid_template_engine.php | 820 ++++++++++++++++++++---------------------- tests/test_short/comment | 1 - tests/test_short/want | 2 +- 4 files changed, 395 insertions(+), 433 deletions(-) delete mode 100644 tests/test_short/comment (limited to 'tests/test_short/want') diff --git a/docu/language_definition.html b/docu/language_definition.html index 77cb680..934042d 100644 --- a/docu/language_definition.html +++ b/docu/language_definition.html @@ -223,7 +223,6 @@

<ste:if>condition<ste:then>then</ste:then><ste:else>else</ste:else></ste:if>

?, {, | and } can be escaped

In this variant, the else part is not optional!

-

WARNING: short if-clauses can not be nested!

ste:cmp

With the ste:cmp tag you can compare two values.

@@ -285,9 +284,7 @@

~{a|operator|b}

This is equivalent to:

<ste:cmp text_a="a" op="operator" text_b="b" />

-

~, {, | and } can be escaped

-

Because this is implemented as a simple substitution, you can only use Text and Variables. And " must be escaped.

-

WARNING: short comparisons can not be nested! They can be inside short if-clauses, but not the other way around!

+

~, {, | and } can be escaped.

ste:not

The ste:not Tag will logically invert its content. If it is an empty text (i.e. false), it will return a non-empty text (i.e. true) and vice versa.

diff --git a/stupid_template_engine.php b/stupid_template_engine.php index 2cfb9d0..cfb5882 100644 --- a/stupid_template_engine.php +++ b/stupid_template_engine.php @@ -15,19 +15,16 @@ */ namespace ste; -abstract class ASTNode -{ +abstract class ASTNode { public $tpl; public $offset; - public function __construct($tpl, $off) - { + public function __construct($tpl, $off) { $this->tpl = $tpl; $this->offset = $off; } } -class TextNode extends ASTNode -{ +class TextNode extends ASTNode { public $text; public function __construct($tpl, $off, $text = "") { parent::__construct($tpl, $off); @@ -35,8 +32,7 @@ class TextNode extends ASTNode } } -class TagNode extends ASTNode -{ +class TagNode extends ASTNode { public $name; public $params = array(); public $sub = array(); @@ -46,53 +42,44 @@ class TagNode extends ASTNode } } -class VariableNode extends ASTNode -{ +class VariableNode extends ASTNode { public $name; public $arrayfields = array(); - public function transcompile() - { + public function transcompile() { $varaccess = '@$ste->vars[' . (is_numeric($this->name) ? $this->name : '"' . escape_text($this->name) . '"'). ']'; - foreach($this->arrayfields as $af) - { - if((count($af) == 1) and ($af[0] instanceof TextNode) and is_numeric($af[0]->text)) + foreach($this->arrayfields as $af) { + if((count($af) == 1) and ($af[0] instanceof TextNode) and is_numeric($af[0]->text)) { $varaccess .= '[' . $af->text . ']'; - else - $varaccess .= '[' . implode(".", - array_map( - function($node) - { - if($node instanceof TextNode) - return "\"" . escape_text($node->text) . "\""; - else if($node instanceof VariableNode) - return $node->transcompile(); - }, $af - ) - ). ']'; + } else { + $varaccess .= '[' . implode(".", array_map(function($node) { + if($node instanceof TextNode) { + return "\"" . escape_text($node->text) . "\""; + } else if($node instanceof VariableNode) { + return $node->transcompile(); + } + }, $af)). ']'; + } } return $varaccess; } } -class ParseCompileError extends \Exception -{ +class ParseCompileError extends \Exception { public $msg; public $tpl; public $off; - public function __construct($msg, $tpl, $offset, $code = 0, $previous = NULL) - { + public function __construct($msg, $tpl, $offset, $code = 0, $previous = NULL) { $this->msg = $msg; $this->tpl = $tpl; $this->off = $offset; $this->message = "$msg (Template $tpl, Offset $offset)"; } - public function rewrite($code) - { + public function rewrite($code) { $line = substr_count(str_replace("\r\n", "\n", substr($code, 0, $this->off)), "\n") + 1; $this->message = "{$this->msg} (Template {$this->tpl}, Line $line)"; - $this->is_rewritten = True; + $this->is_rewritten = true; } } @@ -558,20 +545,15 @@ class Parser { } } -function indent_code($code) -{ +function indent_code($code) { return implode( "\n", - array_map( - function($line) { return "\t$line"; }, - explode("\n", $code) - ) + array_map(function($line) { return "\t$line"; }, explode("\n", $code)) ); } /* We could also just eval() the $infix_math code, but this is much cooler :-D (Parser inception) */ -function shunting_yard($infix_math) -{ +function shunting_yard($infix_math) { $operators = array( "+" => array("l", 2), "-" => array("l", 2), @@ -584,165 +566,156 @@ function shunting_yard($infix_math) ); preg_match_all("/\s*(?:(?:[+\\-\\*\\/\\^\\(\\)])|(\\d*[\\.]?\\d*))\\s*/s", $infix_math, $tokens, PREG_PATTERN_ORDER); - $tokens_raw = array_filter(array_map('trim', $tokens[0]), function($x) { return ($x === "0") or (!empty($x)); }); + $tokens_raw = array_filter(array_map('trim', $tokens[0]), function($x) { return ($x === "0") || (!empty($x)); }); $output_queue = array(); $op_stack = array(); $lastpriority = NULL; /* Make - unary, if neccessary */ $tokens = array(); - foreach($tokens_raw as $token) - { + foreach($tokens_raw as $token) { $priority = isset($operators[$token]) ? $operators[$token][1] : -1; - if(($token == "-") and (($lastpriority === NULL) or ($lastpriority >= 0))) - { + if(($token == "-") && (($lastpriority === NULL) || ($lastpriority >= 0))) { $priority = $operators["_"][1]; $tokens[] = "_"; - } - else + } else { $tokens[] = $token; + } $lastpriority = $priority; } - while(!empty($tokens)) - { + while(!empty($tokens)) { $token = array_shift($tokens); - if(is_numeric($token)) + if(is_numeric($token)) { $output_queue[] = $token; - else if($token == "(") + } else if($token == "(") { $op_stack[] = $token; - else if($token == ")") - { - $lbr_found = False; - while(!empty($op_stack)) - { + } else if($token == ")") { + $lbr_found = false; + while(!empty($op_stack)) { $op = array_pop($op_stack); - if($op == "(") - { - $lbr_found = True; + if($op == "(") { + $lbr_found = true; break; } $output_queue[] = $op; } - if(!$lbr_found) + if(!$lbr_found) { throw new RuntimeError("Bracket mismatch."); - } - else if(!isset($operators[$token])) + } + } else if(!isset($operators[$token])) { throw new RuntimeError("Invalid token ($token): Not a number, bracket or operator. Stop."); - else - { + } else { $priority = $operators[$token][1]; - if($operators[$token][0] == "l") - while((!empty($op_stack)) and ($priority <= $operators[$op_stack[count($op_stack)-1]][1])) + if($operators[$token][0] == "l") { + while((!empty($op_stack)) and ($priority <= $operators[$op_stack[count($op_stack)-1]][1])) { $output_queue[] = array_pop($op_stack); - else - while((!empty($op_stack)) and ($priority < $operators[$op_stack[count($op_stack)-1]][1])) + } + } else { + while((!empty($op_stack)) and ($priority < $operators[$op_stack[count($op_stack)-1]][1])) { $output_queue[] = array_pop($op_stack); + } + } $op_stack[] = $token; } } - while(!empty($op_stack)) - { + while(!empty($op_stack)) { $op = array_pop($op_stack); - if($op == "(") + if($op == "(") { throw new RuntimeError("Bracket mismatch..."); + } $output_queue[] = $op; } return $output_queue; } -function pop2(&$array) -{ +function pop2(&$array) { $rv = array(array_pop($array), array_pop($array)); - if(array_search(NULL, $rv, True) !== False) + if(array_search(NULL, $rv, true) !== false) { throw new RuntimeError("Not enough numbers on stack. Invalid formula."); + } return $rv; } -function calc_rpn($rpn) -{ +function calc_rpn($rpn) { $stack = array(); - foreach($rpn as $token) - { - switch($token) - { - case "+": - list($b, $a) = pop2($stack); - $stack[] = $a + $b; - break; - case "-": - list($b, $a) = pop2($stack); - $stack[] = $a - $b; - break; - case "*": - list($b, $a) = pop2($stack); - $stack[] = $a * $b; - break; - case "/": - list($b, $a) = pop2($stack); - $stack[] = $a / $b; - break; - case "^": - list($b, $a) = pop2($stack); - $stack[] = pow($a, $b); - break; - case "_": - $a = array_pop($stack); - if($a === NULL) - throw new RuntimeError("Not enough numbers on stack. Invalid formula."); - $stack[] = -$a; - break; - default: - $stack[] = $token; - break; + foreach($rpn as $token) { + switch($token) { + case "+": + list($b, $a) = pop2($stack); + $stack[] = $a + $b; + break; + case "-": + list($b, $a) = pop2($stack); + $stack[] = $a - $b; + break; + case "*": + list($b, $a) = pop2($stack); + $stack[] = $a * $b; + break; + case "/": + list($b, $a) = pop2($stack); + $stack[] = $a / $b; + break; + case "^": + list($b, $a) = pop2($stack); + $stack[] = pow($a, $b); + break; + case "_": + $a = array_pop($stack); + if($a === NULL) { + throw new RuntimeError("Not enough numbers on stack. Invalid formula."); + } + $stack[] = -$a; + break; + default: + $stack[] = $token; + break; } } return array_pop($stack); } -function loopbody($code) -{ +function loopbody($code) { return "try\n{\n" . indent_code($code) . "\n}\ncatch(\ste\BreakException \$e) { break; }\ncatch(\ste\ContinueException \$e) { continue; }\n"; } $ste_builtins = array( - "if" => function($ast) - { + "if" => function($ast) { $output = ""; $condition = array(); $then = NULL; $else = NULL; - foreach($ast->sub as $node) - { - if(($node instanceof TagNode) and ($node->name == "then")) + foreach($ast->sub as $node) { + if(($node instanceof TagNode) and ($node->name == "then")) { $then = $node->sub; - else if(($node instanceof TagNode) and ($node->name == "else")) + } else if(($node instanceof TagNode) and ($node->name == "else")) { $else = $node->sub; - else + } else { $condition[] = $node; + } } - if($then === NULL) + if($then === NULL) { throw new ParseCompileError("Transcompile error: Missing in .", $ast->tpl, $ast->offset); + } $output .= "\$outputstack[] = \"\";\n\$outputstack_i++;\n"; $output .= _transcompile($condition); $output .= "\$outputstack_i--;\nif(\$ste->evalbool(array_pop(\$outputstack)))\n{\n"; $output .= indent_code(_transcompile($then)); $output .= "\n}\n"; - if($else !== NULL) - { + if($else !== NULL) { $output .= "else\n{\n"; $output .= indent_code(_transcompile($else)); $output .= "\n}\n"; } return $output; }, - "cmp" => function($ast) - { + "cmp" => function($ast) { $operators = array( array('eq', '=='), array('neq', '!='), @@ -754,42 +727,48 @@ $ste_builtins = array( $code = ""; - if(isset($ast->params["var_b"])) - $b = '$ste->get_var_by_name(' . _transcompile($ast->params["var_b"], True) . ')'; - else if(isset($ast->params["text_b"])) - $b = _transcompile($ast->params["text_b"], True); - else + if(isset($ast->params["var_b"])) { + list($val, $pre) = _transcompile($ast->params["var_b"], true); + $code .= $pre; + $b = '$ste->get_var_by_name(' . $val . ')'; + } else if(isset($ast->params["text_b"])) { + list($b, $pre) = _transcompile($ast->params["text_b"], true); + $code .= $pre; + } else { throw new ParseCompileError("Transcompile error: neiter var_b nor text_b set in .", $ast->tpl, $ast->offset); + } - if(isset($ast->params["var_a"])) - $a = '$ste->get_var_by_name(' . _transcompile($ast->params["var_a"], True) . ')'; - else if(isset($ast->params["text_a"])) - $a = _transcompile($ast->params["text_a"], True); - else + if(isset($ast->params["var_a"])) { + list($val, $pre) = _transcompile($ast->params["var_a"], true); + $code .= $pre; + $a = '$ste->get_var_by_name(' . $val . ')'; + } else if(isset($ast->params["text_a"])) { + list($a, $pre) = _transcompile($ast->params["text_a"], true); + $code .= $pre; + } else { throw new ParseCompileError("Transcompile error: neiter var_a nor text_a set in .", $ast->tpl, $ast->offset); + } - if(!isset($ast->params["op"])) + if(!isset($ast->params["op"])) { throw new ParseCompileError("Transcompile error: op not given in .", $ast->tpl, $ast->offset); - if((count($ast->params["op"]) == 1) and ($ast->params["op"][0] instanceof TextNode)) - { + } + if((count($ast->params["op"]) == 1) and ($ast->params["op"][0] instanceof TextNode)) { /* Operator is known at compile time, this saves *a lot* of output code! */ $op = trim($ast->params["op"][0]->text); $op_php = NULL; - foreach($operators as $v) - { - if($v[0] == $op) - { + foreach($operators as $v) { + if($v[0] == $op) { $op_php = $v[1]; break; } } - if($op_php === NULL) + if($op_php === NULL) { throw new ParseCompileError("Transcompile Error: Unknown operator in ", $ast->tpl, $ast->offset); + } $code .= "\$outputstack[\$outputstack_i] .= (($a) $op_php ($b)) ? 'yes' : '';\n"; - } - else - { - $code .= "switch(trim(" . _transcompile($ast->params["op"], True) . "))\n{\n\t"; + } else { + list($val, $pre) = _transcompile($ast->params["op"], true); + $code .= $pre . "switch(trim(" . $val . "))\n{\n\t"; $code .= implode("", array_map( function($op) use ($a,$b) { @@ -801,49 +780,57 @@ $ste_builtins = array( } return $code; }, - "not" => function($ast) - { + "not" => function($ast) { $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->sub); $code .= "\$outputstack_i--;\n\$outputstack[\$outputstack_i] .= (!\$ste->evalbool(array_pop(\$outputstack))) ? 'yes' : '';\n"; return $code; }, - "even" => function($ast) - { + "even" => function($ast) { $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->sub); $code .= "\$outputstack_i--;\n\$tmp_even = array_pop(\$outputstack);\n\$outputstack[\$outputstack_i] .= (is_numeric(\$tmp_even) and (\$tmp_even % 2 == 0)) ? 'yes' : '';\n"; return $code; }, - "for" => function($ast) - { + "for" => function($ast) { $code = ""; - $loopname = "forloop_" . str_replace(".", "_", uniqid("",True)); - if(empty($ast->params["start"])) + $loopname = "forloop_" . str_replace(".", "_", uniqid("",true)); + if(empty($ast->params["start"])) { throw new ParseCompileError("Transcompile error: Missing 'start' parameter in .", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_start = " . _transcompile($ast->params["start"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["start"], true); + $code .= $pre; + $code .= "\$${loopname}_start = " . $val . ";\n"; - if(empty($ast->params["stop"])) + if(empty($ast->params["stop"])) { throw new ParseCompileError("Transcompile error: Missing 'end' parameter in .", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_stop = " . _transcompile($ast->params["stop"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["stop"], true); + $code .= $pre; + $code .= "\$${loopname}_stop = " . $val . ";\n"; $step = NULL; /* i.e. not known at compilation time */ - if(empty($ast->params["step"])) + if(empty($ast->params["step"])) { $step = 1; - else if((count($ast->params["step"]) == 1) and ($ast->params["step"][0] instanceof TextNode)) + } else if((count($ast->params["step"]) == 1) and ($ast->params["step"][0] instanceof TextNode)) { $step = $ast->params["step"][0]->text + 0; - else - $code .= "\$${loopname}_step = " . _transcompile($ast->params["step"], True) . ";\n"; + } else { + list($val, $pre) = _transcompile($ast->params["step"], true); + $code .= $pre; + $code .= "\$${loopname}_step = " . $val . ";\n"; + } - if(!empty($ast->params["counter"])) - $code .= "\$${loopname}_countername = " . _transcompile($ast->params["counter"], True) . ";\n"; + if(!empty($ast->params["counter"])) { + list($val, $pre) = _transcompile($ast->params["counter"], true); + $code .= $pre; + $code .= "\$${loopname}_countername = " . $val . ";\n"; + } $loopbody = empty($ast->params["counter"]) ? "" : "\$ste->set_var_by_name(\$${loopname}_countername, \$${loopname}_counter);\n"; $loopbody .= _transcompile($ast->sub); $loopbody = indent_code("{\n" . loopbody(indent_code($loopbody)) . "\n}\n"); - if($step === NULL) - { + if($step === NULL) { $code .= "if(\$${loopname}_step == 0)\n\tthrow new \\ste\\RuntimeError('step can not be 0 in .');\n"; $code .= "if(\$${loopname}_step > 0)\n{\n"; $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n"; @@ -852,47 +839,58 @@ $ste_builtins = array( $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter >= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n"; $code .= $loopbody; $code .= "\n}\n"; - } - else if($step == 0) + } else if($step == 0) { throw new ParseCompileError("Transcompile Error: step can not be 0 in .", $ast->tpl, $ast->offset); - else if($step > 0) + } else if($step > 0) { $code .= "for(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += $step)\n$loopbody\n"; - else + } else { $code .= "for(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter >= \$${loopname}_stop; \$${loopname}_counter += $step)\n$loopbody\n"; + } return $code; }, - "foreach" => function($ast) - { - $loopname = "foreachloop_" . str_replace(".", "_", uniqid("",True)); + "foreach" => function($ast) { + $loopname = "foreachloop_" . str_replace(".", "_", uniqid("",true)); $code = ""; - if(empty($ast->params["array"])) + if(empty($ast->params["array"])) { throw new ParseCompileError("Transcompile Error: array not given in .", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_arrayvar = " . _transcompile($ast->params["array"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["array"], true); + $code .= $pre; + $code .= "\$${loopname}_arrayvar = " . $val . ";\n"; - if(empty($ast->params["value"])) + if(empty($ast->params["value"])) { throw new ParseCompileError("Transcompile Error: value not given in .", $ast->tpl, $ast->offset); - $code .= "\$${loopname}_valuevar = " . _transcompile($ast->params["value"], True) . ";\n"; - - if(!empty($ast->params["key"])) - $code .= "\$${loopname}_keyvar = " . _transcompile($ast->params["key"], True) . ";\n"; + } + list($val, $pre) = _transcompile($ast->params["value"], true); + $code .= $pre; + $code .= "\$${loopname}_valuevar = " . $val . ";\n"; + + if(!empty($ast->params["key"])) { + list($val, $pre) = _transcompile($ast->params["key"], true); + $code .= $pre; + $code .= "\$${loopname}_keyvar = " . $val . ";\n"; + } - if(!empty($ast->params["counter"])) - $code .= "\$${loopname}_countervar = " . _transcompile($ast->params["counter"], True) . ";\n"; + if(!empty($ast->params["counter"])) { + list($val, $pre) = _transcompile($ast->params["counter"], true); + $code .= $pre; + $code .= "\$${loopname}_countervar = " . $val . ";\n"; + } $loopbody = ""; $code .= "\$${loopname}_array = \$ste->get_var_by_name(\$${loopname}_arrayvar);\n"; $code .= "if(!is_array(\$${loopname}_array))\n\t\$${loopname}_array = array();\n"; - if(!empty($ast->params["counter"])) - { + if(!empty($ast->params["counter"])) { $code .= "\$${loopname}_counter = -1;\n"; $loopbody .= "\$${loopname}_counter++;\n\$ste->set_var_by_name(\$${loopname}_countervar, \$${loopname}_counter);\n"; } $loopbody .= "\$ste->set_var_by_name(\$${loopname}_valuevar, \$${loopname}_value);\n"; - if(!empty($ast->params["key"])) + if(!empty($ast->params["key"])) { $loopbody .= "\$ste->set_var_by_name(\$${loopname}_keyvar, \$${loopname}_key);\n"; + } $loopbody .= "\n"; $loopbody .= _transcompile($ast->sub); $loopbody = "{\n" . loopbody(indent_code($loopbody)) . "\n}\n"; @@ -901,58 +899,57 @@ $ste_builtins = array( return $code; }, - "infloop" => function($ast) - { - return "while(True)\n{\n" . indent_code(loopbody(indent_code(_transcompile($ast->sub)))) . "\n}\n"; + "infloop" => function($ast) { + return "while(true)\n{\n" . indent_code(loopbody(indent_code(_transcompile($ast->sub)))) . "\n}\n"; }, - "break" => function($ast) - { + "break" => function($ast) { return "throw new \\ste\\BreakException();\n"; }, - "continue" => function($ast) - { + "continue" => function($ast) { return "throw new \\ste\\ContinueException();\n"; }, - "block" => function($ast) - { - if(empty($ast->params["name"])) + "block" => function($ast) { + if(empty($ast->params["name"])) { throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); + } - $blknamevar = "blockname_" . str_replace(".", "_", uniqid("", True)); + $blknamevar = "blockname_" . str_replace(".", "_", uniqid("", true)); - $code = "\$${blknamevar} = " . _transcompile($ast->params["name"], True) . ";\n"; + list($val, $code) = _transcompile($ast->params["name"], true); + $code .= "\$${blknamevar} = " . $val . ";\n"; - $tmpblk = uniqid("", True); + $tmpblk = uniqid("", true); $code .= "\$ste->blocks['$tmpblk'] = array_pop(\$outputstack);\n\$ste->blockorder[] = '$tmpblk';\n\$outputstack = array('');\n\$outputstack_i = 0;\n"; $code .= _transcompile($ast->sub); $code .= "\$ste->blocks[\$${blknamevar}] = array_pop(\$outputstack);\n"; - $code .= "if(array_search(\$${blknamevar}, \$ste->blockorder) === FALSE)\n\t\$ste->blockorder[] = \$${blknamevar};\n\$outputstack = array('');\n\$outputstack_i = 0;\n"; + $code .= "if(array_search(\$${blknamevar}, \$ste->blockorder) === false)\n\t\$ste->blockorder[] = \$${blknamevar};\n\$outputstack = array('');\n\$outputstack_i = 0;\n"; return $code; }, - "load" => function($ast) - { - if(empty($ast->params["name"])) + "load" => function($ast) { + if(empty($ast->params["name"])) { throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); + } - return "\$outputstack[\$outputstack_i] .= \$ste->load(" . _transcompile($ast->params["name"], True) . ");\n"; + list($val, $code) = _transcompile($ast->params["name"], true); + $code .= "\$outputstack[\$outputstack_i] .= \$ste->load(" . $val . ");\n"; + return $code; }, - "mktag" => function($ast) - { + "mktag" => function($ast) { $code = ""; - if(empty($ast->params["name"])) + if(empty($ast->params["name"])) { throw new ParseCompileError("Transcompile Error: name missing in .", $ast->tpl, $ast->offset); - - $tagname = _transcompile($ast->params["name"], True); + } $fxbody = "\$outputstack = array(''); \$outputstack_i = 0;\$ste->vars['_tag_parameters'] = \$params;\n"; + list($tagname, $tagname_pre) = _transcompile($ast->params["name"], true); + $usemandatory = ""; - if(!empty($ast->params["mandatory"])) - { + if(!empty($ast->params["mandatory"])) { $usemandatory = " use (\$mandatory_params)"; $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->params["mandatory"]); @@ -965,36 +962,39 @@ $ste_builtins = array( $fxbody .= "return array_pop(\$outputstack);"; $code .= "\$tag_fx = function(\$ste, \$params, \$sub)" . $usemandatory . "\n{\n" . indent_code($fxbody) . "\n};\n"; + $code .= $tagname_pre; $code .= "\$ste->register_tag($tagname, \$tag_fx);\n"; return $code; }, - "tagcontent" => function($ast) - { + "tagcontent" => function($ast) { return "\$outputstack[\$outputstack_i] .= \$sub(\$ste);"; }, - "set" => function($ast) - { - if(empty($ast->params["var"])) + "set" => function($ast) { + if(empty($ast->params["var"])) { throw new ParseCompileError("Transcompile Error: var missing in .", $ast->tpl, $ast->offset); + } $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->sub); $code .= "\$outputstack_i--;\n"; - $code .= "\$ste->set_var_by_name(" . _transcompile($ast->params["var"], True) . ", array_pop(\$outputstack));\n"; + list($val, $pre) = _transcompile($ast->params["var"], true); + $code .= $pre; + $code .= "\$ste->set_var_by_name(" . $val . ", array_pop(\$outputstack));\n"; return $code; }, - "get" => function($ast) - { - if(empty($ast->params["var"])) + "get" => function($ast) { + if(empty($ast->params["var"])) { throw new ParseCompileError("Transcompile Error: var missing in .", $ast->tpl, $ast->offset); + } - return "\$outputstack[\$outputstack_i] .= \$ste->get_var_by_name(" . _transcompile($ast->params["var"], True) . ");"; + list($val, $pre) = _transcompile($ast->params["var"], true); + $code .= $pre; + return "\$outputstack[\$outputstack_i] .= \$ste->get_var_by_name(" . $val . ");"; }, - "calc" => function($ast) - { + "calc" => function($ast) { $code = "\$outputstack[] = '';\n\$outputstack_i++;\n"; $code .= _transcompile($ast->sub); $code .= "\$outputstack_i--;\n\$outputstack[\$outputstack_i] .= \$ste->calc(array_pop(\$outputstack));\n"; @@ -1003,40 +1003,36 @@ $ste_builtins = array( } ); -function escape_text($text) -{ +function escape_text($text) { return addcslashes($text, "\r\n\t\$\0..\x1f\\\"\x7f..\xff"); } -function _transcompile($ast, $no_outputstack = False) /* The real transcompile function, does not add boilerplate code. */ -{ +function _transcompile($ast, $avoid_outputstack = false) { /* The real transcompile function, does not add boilerplate code. */ $code = ""; global $ste_builtins; $text_and_var_buffer = array(); - foreach($ast as $node) - { - if($node instanceof TextNode) + foreach($ast as $node) { + if($node instanceof TextNode) { $text_and_var_buffer[] = '"' . escape_text($node->text) . '"'; - else if($node instanceof VariableNode) + } else if($node instanceof VariableNode) { $text_and_var_buffer[] = $node->transcompile(); - else if($node instanceof TagNode) - { - if(!empty($text_and_var_buffer)) - { + } else if($node instanceof TagNode) { + if(!empty($text_and_var_buffer)) { $code .= "\$outputstack[\$outputstack_i] .= " . implode (" . ", $text_and_var_buffer) . ";\n"; $text_and_var_buffer = array(); } - if(isset($ste_builtins[$node->name])) + if(isset($ste_builtins[$node->name])) { $code .= $ste_builtins[$node->name]($node); - else - { - $paramarray = "parameters_" . str_replace(".", "_", uniqid("", True)); + } else { + $paramarray = "parameters_" . str_replace(".", "_", uniqid("", true)); $code .= "\$$paramarray = array();\n"; - foreach($node->params as $pname => $pcontent) - $code .= "\$${paramarray}['" . escape_text($pname) . "'] = " . _transcompile($pcontent, True) . ";\n"; + foreach($node->params as $pname => $pcontent) { + list($pval, $pre) = _transcompile($pcontent, true); + $code .= $pre . "\$${paramarray}['" . escape_text($pname) . "'] = " . $pval . ";\n"; + } $code .= "\$outputstack[\$outputstack_i] .= \$ste->call_tag('" . escape_text($node->name) . "', \$${paramarray}, "; $code .= empty($node->sub) ? "function(\$ste) { return ''; }" : transcompile($node->sub); @@ -1045,18 +1041,19 @@ function _transcompile($ast, $no_outputstack = False) /* The real transcompile f } } - if(!empty($text_and_var_buffer)) - { - if(!$no_outputstack) - $code .= "\$outputstack[\$outputstack_i] .= "; - $code .= implode (" . ", $text_and_var_buffer); - if(!$no_outputstack) - $code .= ";\n"; - $text_and_var_buffer = array(); + if($avoid_outputstack && ($code == "")) { + return array(implode (" . ", $text_and_var_buffer), ""); } - else if($no_outputstack) - { - $code = "\"\""; + + if(!empty($text_and_var_buffer)) { + $code .= "\$outputstack[\$outputstack_i] .= ". implode (" . ", $text_and_var_buffer) . ";\n"; + } + + if($avoid_outputstack) { + $tmpvar = "tmp_" . str_replace(".", "_", uniqid("",true)); + $code = "\$outputstack[] = '';\n\$outputstack_i++;" . $code; + $code .= "\$$tmpvar = array_pop(\$outputstack);\n\$outputstack_i--;\n"; + return array("\$$tmpvar", $code); } return $code; @@ -1075,8 +1072,7 @@ $ste_transc_boilerplate = "\$outputstack = array('');\n\$outputstack_i = 0;\n"; * Returns: * PHP code. The PHP code is an anonymous function expecting a instance as its parameter and returns a string (everything that was not pached into a section). */ -function transcompile($ast) /* Transcompile and add some boilerplate code. */ -{ +function transcompile($ast) { /* Transcompile and add some boilerplate code. */ global $ste_transc_boilerplate; return "function(\$ste)\n{\n" . indent_code($ste_transc_boilerplate . _transcompile($ast) . "return array_pop(\$outputstack);") . "\n}"; } @@ -1110,8 +1106,7 @@ class CantSaveTemplate extends StorageAccessFailure { } * A StorageAccess implementation is used to access the templates from any storage. * This means, that you are not limited to store the Templates inside directories, you can also use a database or something else. */ -interface StorageAccess -{ +interface StorageAccess { /* * Function: load * Loading a template. @@ -1149,8 +1144,7 @@ interface StorageAccess * Class: FilesystemStorageAccess * The default implementation for loading / saving templates into a directory structure. */ -class FilesystemStorageAccess implements StorageAccess -{ +class FilesystemStorageAccess implements StorageAccess { protected $sourcedir; protected $transcompileddir; @@ -1161,62 +1155,51 @@ class FilesystemStorageAccess implements StorageAccess * $src - The directory with the sources (Writing permissions are not mandatory, because STE does not save template sources). * $transc - The directory with the transcompiled templates (the PHP instance / the HTTP Server needs writing permissions to this directory). */ - public function __construct($src, $transc) - { + public function __construct($src, $transc) { $this->sourcedir = $src; $this->transcompileddir = $transc; } - public function load($tpl, &$mode) - { + public function load($tpl, &$mode) { $src_fn = $this->sourcedir . "/" . $tpl; $transc_fn = $this->transcompileddir . "/" . $tpl . ".php"; - if($mode == MODE_SOURCE) - { + if($mode == MODE_SOURCE) { $content = @file_get_contents($src_fn); - if($content === False) + if($content === false) { throw new CantLoadTemplate("Template not found."); + } return $content; } $src_stat = @stat($src_fn); $transc_stat = @stat($transc_fn); - if(($src_stat === False) and ($transc_stat === False)) + if(($src_stat === false) and ($transc_stat === false)) { throw new CantLoadTemplate("Template not found."); - else if($transc_stat === False) - { + } else if($transc_stat === false) { $mode = MODE_SOURCE; return file_get_contents($src_fn); - } - else if($src_stat === False) - { + } else if($src_stat === false) { include($transc_fn); return $transcompile_fx; - } - else - { - if($src_stat["mtime"] > $transc_stat["mtime"]) - { + } else { + if($src_stat["mtime"] > $transc_stat["mtime"]) { $mode = MODE_SOURCE; return file_get_contents($src_fn); - } - else - { + } else { include($transc_fn); return $transcompile_fx; } } } - public function save($tpl, $data, $mode) - { + public function save($tpl, $data, $mode) { $fn = (($mode == MODE_SOURCE) ? $this->sourcedir : $this->transcompileddir) . "/" . $tpl . (($mode == MODE_TRANSCOMPILED) ? ".php" : ""); - @mkdir(dirname($fn), 0777, True); - if(file_put_contents($fn, "") === False) + @mkdir(dirname($fn), 0777, true); + if(file_put_contents($fn, "") === false) { throw new CantSaveTemplate("Unable to save template."); - + } } } @@ -1227,8 +1210,7 @@ class ContinueException extends \Exception { } * Class: STECore * The Core of STE */ -class STECore -{ +class STECore { private $tags; private $storage_access; private $cur_tpl_dir; @@ -1239,14 +1221,14 @@ class STECore * $blocks - Associative array of blocks (see the language definition). * $blockorder - The order of the blocks (an array) * $vars - Associative array of all template variables. Use this to pass data to your templates. - * $mute_runtime_errors - If True (default) a exception will result in no output from the tag, if False a error message will be written to output. - * $fatal_error_on_missing_tag - If True, STE will throw a if a tag was called that was not registered, otherwise (default) a regular will be thrown and automatically handled by STE (see <$mute_runtime_errors>). + * $mute_runtime_errors - If true (default) a exception will result in no output from the tag, if false a error message will be written to output. + * $fatal_error_on_missing_tag - If true, STE will throw a if a tag was called that was not registered, otherwise (default) a regular will be thrown and automatically handled by STE (see <$mute_runtime_errors>). */ public $blocks; public $blockorder; public $vars; - public $mute_runtime_errors = True; - public $fatal_error_on_missing_tag = False; + public $mute_runtime_errors = true; + public $fatal_error_on_missing_tag = false; /* * Constructor: __construct @@ -1254,8 +1236,7 @@ class STECore * Parameters: * $storage_access - An Instance of a implementation. */ - public function __construct($storage_access) - { + public function __construct($storage_access) { $this->storage_access = $storage_access; $this->cur_tpl_dir = "/"; STEStandardLibrary::_register_lib($this); @@ -1275,12 +1256,13 @@ class STECore * Throws: * An Exception if the tag could not be registered (if $callback is not callable or if $name is empty) */ - public function register_tag($name, $callback) - { - if(!is_callable($callback)) + public function register_tag($name, $callback) { + if(!is_callable($callback)) { throw new \Exception("Can not register tag \"$name\", not callable."); - if(empty($name)) + } + if(empty($name)) { throw new \Exception("Can not register tag, empty name."); + } $this->tags[$name] = $callback; } @@ -1299,28 +1281,24 @@ class STECore * Returns: * The output of the tag or, if a was thrown, the appropiate result (see <$mute_runtime_errors>). */ - public function call_tag($name, $params, $sub) - { - try - { - if(!isset($this->tags[$name])) - { - if($this->fatal_error_on_missing_tag) + public function call_tag($name, $params, $sub) { + try { + if(!isset($this->tags[$name])) { + if($this->fatal_error_on_missing_tag) { throw new FatalRuntimeError("Can not call tag \"$name\": Does not exist."); - else + } else { throw new RuntimeError("Can not call tag \"$name\": Does not exist."); + } } return call_user_func($this->tags[$name], $this, $params, $sub); - } - catch(RuntimeError $e) - { - if(!$this->mute_runtime_errors) + } catch(RuntimeError $e) { + if(!$this->mute_runtime_errors) { return "RuntimeError occurred on tag '$name': " . $e->getMessage(); + } } } - public function calc($expression) - { + public function calc($expression) { return calc_rpn(shunting_yard($expression)); } @@ -1340,13 +1318,13 @@ class STECore * Returns: * The output of the template. */ - public function exectemplate($tpl) - { + public function exectemplate($tpl) { $output = ""; $lastblock = $this->load($tpl); - foreach($this->blockorder as $blockname) + foreach($this->blockorder as $blockname) { $output .= $this->blocks[$blockname]; + } return $output . $lastblock; } @@ -1367,47 +1345,39 @@ class STECore * A Reference to the variable. */ - public function &get_var_reference($name, $create_if_not_exist) - { + public function &get_var_reference($name, $create_if_not_exist) { $ref = &$this->_get_var_reference($this->vars, $name, $create_if_not_exist); return $ref; } - private function &_get_var_reference(&$from, $name, $create_if_not_exist) - { + private function &_get_var_reference(&$from, $name, $create_if_not_exist) { $bracket_open = strpos($name, "["); - if($bracket_open === False) - { - if(isset($from[$name]) or $create_if_not_exist) - { + if($bracket_open === false) { + if(isset($from[$name]) or $create_if_not_exist) { $ref = &$from[$name]; return $ref; - } - else + } else { return NULL; - } - else - { + } + } else { $old_varname = $varname; $bracket_close = strpos($name, "]", $bracket_open); - if($bracket_close === FALSE) + if($bracket_close === false) { throw new RuntimeError("Invalid varname \"$varname\". Missing closing \"]\"."); + } $varname = substr($name, 0, $bracket_open); $name = substr($name, $bracket_open + 1, $bracket_close - $bracket_open - 1) . substr($name, $bracket_close + 1); - if(!is_array($from[$varname])) - { - if($create_if_not_exist) + if(!is_array($from[$varname])) { + if($create_if_not_exist) { $from[$varname] = array(); - else + } else { return NULL; + } } - try - { + try { $ref = &$this->_get_var_reference($from[$varname], $name, $create_if_not_exist); return $ref; - } - catch(Exception $e) - { + } catch(Exception $e) { throw new RuntimeError("Invalid varname \"$old_varname\". Missing closing \"]\"."); } } @@ -1425,9 +1395,8 @@ class STECore * Throws: * if the variable name can not be parsed (e.g. unbalanced brackets). */ - public function set_var_by_name($name, $val) - { - $ref = &$this->_get_var_reference($this->vars, $name, True); + public function set_var_by_name($name, $val) { + $ref = &$this->_get_var_reference($this->vars, $name, true); $ref = $val; } @@ -1445,9 +1414,8 @@ class STECore * Returns: * The variables value. */ - public function get_var_by_name($name) - { - $ref = $this->_get_var_reference($this->vars, $name, False); + public function get_var_by_name($name) { + $ref = $this->_get_var_reference($this->vars, $name, false); return $ref === NULL ? "" : $ref; } @@ -1468,43 +1436,38 @@ class STECore * Returns: * The result of the template (if $quiet == false). */ - public function load($tpl, $quiet=False) - { + public function load($tpl, $quiet = false) { $tpldir_b4 = $this->cur_tpl_dir; /* Resolve ".", ".." and protect from possible LFI */ $tpl = str_replace("\\", "/", $tpl); - if($tpl[0] != "/") + if($tpl[0] != "/") { $tpl = $this->cur_tpl_dir . "/" . $tpl; + } $pathex = array_filter(explode("/", $tpl), function($s) { return ($s != ".") and (!empty($s)); }); $pathex = array_merge($pathex); - while(($i = array_search("..", $pathex)) !== False) - { - if($i == 0) + while(($i = array_search("..", $pathex)) !== false) { + if($i == 0) { $pathex = array_slice($pathex, 1); - else + } else { $pathex = array_merge(array_slice($pathex, 0, $i), array_slice($pathex, $i + 2)); + } } $tpl = implode("/", $pathex); $this->cur_tpl_dir = dirname($tpl); - if($quiet) - { + if($quiet) { $blocks_back = clone $this->blocks; $blockorder_back = clone $this->blockorder; } $mode = MODE_TRANSCOMPILED; $content = $this->storage_access->load($tpl, $mode); - if($mode == MODE_SOURCE) - { - try - { + if($mode == MODE_SOURCE) { + try { $ast = Parser::parse($content, $tpl); $transc = transcompile($ast); - } - catch(ParseCompileError $e) - { + } catch(ParseCompileError $e) { $e->rewrite($content); throw $e; } @@ -1516,13 +1479,12 @@ class STECore $this->cur_tpl_dir = $tpldir_b4; - if($quiet) - { + if($quiet) { $this->blocks = $blocks_back; $this->blockorder = $blockorder_back; - } - else + } else { return $output; + } } /* @@ -1535,145 +1497,149 @@ class STECore * Returns: * true/false. */ - public function evalbool($txt) - { + public function evalbool($txt) { return trim($txt . "") != ""; } } -class STEStandardLibrary -{ - static public function _register_lib($ste) - { - foreach(get_class_methods(__CLASS__) as $method) - if($method[0] != "_") +class STEStandardLibrary { + static public function _register_lib($ste) { + foreach(get_class_methods(__CLASS__) as $method) { + if($method[0] != "_") { $ste->register_tag($method, array(__CLASS__, $method)); + } + } } - static public function escape($ste, $params, $sub) - { - if($ste->evalbool($params["lines"])) + static public function escape($ste, $params, $sub) { + if($ste->evalbool($params["lines"])) { return nl2br(htmlspecialchars(str_replace("\r\n", "\n", $sub($ste)))); - else + } else { return htmlspecialchars($sub($ste)); + } } - static public function strlen($ste, $params, $sub) - { + static public function strlen($ste, $params, $sub) { return strlen($sub($ste)); } - static public function arraylen($ste, $params, $sub) - { - if(empty($params["array"])) + static public function arraylen($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); - $a = $ste->get_var_by_name($params["array"], False); + } + $a = $ste->get_var_by_name($params["array"], false); return (is_array($a)) ? count($a) : ""; } - static public function inc($ste, $params, $sub) - { - if(empty($params["var"])) + static public function inc($ste, $params, $sub) { + if(empty($params["var"])) { throw new RuntimeError("Missing var parameter in ."); - $ref = &$ste->get_var_reference($params["var"], True); + } + $ref = &$ste->get_var_reference($params["var"], true); $ref++; } - static public function dec($ste, $params, $sub) - { - if(empty($params["var"])) + static public function dec($ste, $params, $sub) { + if(empty($params["var"])) { throw new RuntimeError("Missing var parameter in ."); - $ref = &$ste->get_var_reference($params["var"], True); + } + $ref = &$ste->get_var_reference($params["var"], true); $ref--; } - static public function date($ste, $params, $sub) - { + static public function date($ste, $params, $sub) { return @strftime($sub($ste), empty($params["timestamp"]) ? @time() : (int) $params["timestamp"]); } - static public function in_array($ste, $params, $sub) - { - if(empty($params["array"])) + static public function in_array($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); - $ar = &$ste->get_var_reference($params["array"], False); - if(!is_array($ar)) + } + $ar = &$ste->get_var_reference($params["array"], false); + if(!is_array($ar)) { return ""; + } return in_array($sub($ste), $ar) ? "y" : ""; } - static public function join($ste, $params, $sub) - { - if(empty($params["array"])) + static public function join($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); + } return implode($sub($ste), $ste->get_var_by_name($params["array"])); } - static public function split($ste, $params, $sub) - { - if(empty($params["array"])) + static public function split($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); - if(empty($params["delim"])) + } + if(empty($params["delim"])) { throw new RuntimeError("Missing delim parameter in ."); + } $ste->set_var_by_name($params["array"], explode($params["delim"], $sub($ste))); } - static public function array_add($ste, $params, $sub) - { - if(empty($params["array"])) + static public function array_add($ste, $params, $sub) { + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); + } - $ar = &$ste->get_var_reference($params["array"], True); - if(empty($params["key"])) + $ar = &$ste->get_var_reference($params["array"], true); + if(empty($params["key"])) { $ar[] = $sub($ste); - else + } else { $ar[$params["key"]] = $sub($ste); + } } static public function array_filter($ste, $params, $sub) { - if(empty($params["array"])) + if(empty($params["array"])) { throw new RuntimeError("Missing array parameter in ."); + } $ar = $ste->get_var_by_name($params["array"]); - if(!is_array($ar)) + if(!is_array($ar)) { throw new RuntimeError("Variable at 'array' is not an array."); + } $keys = array_keys($ar); - if(!empty($params["keep_by_keys"])) - { - $keep_by_keys = &$ste->get_var_reference($params["keep_by_keys"], False); - if(!is_array($keep_by_keys)) + if(!empty($params["keep_by_keys"])) { + $keep_by_keys = &$ste->get_var_reference($params["keep_by_keys"], false); + if(!is_array($keep_by_keys)) { throw new RuntimeError("Variable at 'keep_by_keys' is not an array."); + } $delkeys = array_filter($keys, function($k) use ($keep_by_keys) { return !in_array($k, $keep_by_keys); }); - foreach($delkeys as $dk) + foreach($delkeys as $dk) { unset($ar[$dk]); + } $keys = array_keys($ar); } - if(!empty($params["keep_by_values"])) - { - $keep_by_values = &$ste->get_var_reference($params["keep_by_values"], False); - if(!is_array($keep_by_values)) + if(!empty($params["keep_by_values"])) { + $keep_by_values = &$ste->get_var_reference($params["keep_by_values"], false); + if(!is_array($keep_by_values)) { throw new RuntimeError("Variable at 'keep_by_values' is not an array."); + } $ar = array_filter($ar, function($v) use ($keep_by_values) { return in_array($v, $keep_by_values); }); $keys = array_keys($ar); } - if(!empty($params["delete_by_keys"])) - { - $delete_by_keys = &$ste->get_var_reference($params["delete_by_keys"], False); - if(!is_array($delete_by_keys)) + if(!empty($params["delete_by_keys"])) { + $delete_by_keys = &$ste->get_var_reference($params["delete_by_keys"], false); + if(!is_array($delete_by_keys)) { throw new RuntimeError("Variable at 'delete_by_keys' is not an array."); + } $delkeys = array_filter($keys, function($k) use ($delete_by_keys) { return in_array($k, $delete_by_keys); }); - foreach($delkeys as $dk) + foreach($delkeys as $dk) { unset($ar[$dk]); + } $keys = array_keys($ar); } - if(!empty($params["delete_by_values"])) - { - $delete_by_values = &$ste->get_var_reference($params["delete_by_values"], False); - if(!is_array($delete_by_values)) + if(!empty($params["delete_by_values"])) { + $delete_by_values = &$ste->get_var_reference($params["delete_by_values"], false); + if(!is_array($delete_by_values)) { throw new RuntimeError("Variable at 'delete_by_values' is not an array."); + } $ar = array_filter($ar, function($v) use ($delete_by_values) { return !in_array($v, $delete_by_values); }); $keys = array_keys($ar); } diff --git a/tests/test_short/comment b/tests/test_short/comment deleted file mode 100644 index 12d7494..0000000 --- a/tests/test_short/comment +++ /dev/null @@ -1 +0,0 @@ -This currently does not work, since the compiler does not allow tags in tag parameters. diff --git a/tests/test_short/want b/tests/test_short/want index a0aba93..d86bac9 100644 --- a/tests/test_short/want +++ b/tests/test_short/want @@ -1 +1 @@ -OK \ No newline at end of file +OK -- cgit v1.2.3-70-g09d2