summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Chabowski <kevin@kch42.de>2014-05-24 01:39:33 +0200
committerKevin Chabowski <kevin@kch42.de>2014-05-24 01:39:33 +0200
commit84d815f4e006e02521759070bb89025dab80b219 (patch)
tree418a03f0b8be181cb29230ad5e808d3213b4d3cb
parentfdbe2e9521aa54ec6e0b70c1cb0f532248e531e4 (diff)
downloadste-84d815f4e006e02521759070bb89025dab80b219.tar.gz
ste-84d815f4e006e02521759070bb89025dab80b219.tar.bz2
ste-84d815f4e006e02521759070bb89025dab80b219.zip
Added scoping.
ste:mktag generated tags now have an own scope. They even resemble closures, since they inherit their parent scope. A lot of work was done to keep this compatible with older programs. However: * Templates that relied on the non-scoping behavior of tags will probably fail. * Since $ste->vars is no longer an actual array, things like $ste->vars["foo"]["bar"] = "baz" are no longer possible! A single field access will still work: $ste->vars["foo"] = "bar"
-rw-r--r--STECore.php114
-rw-r--r--Scope.php187
-rw-r--r--Transcompiler.php6
-rw-r--r--VarNotInScope.php5
-rw-r--r--ste.php3
-rw-r--r--tests/test_closure/.gitignore3
-rw-r--r--tests/test_closure/code.php5
-rw-r--r--tests/test_closure/test.tpl8
-rw-r--r--tests/test_closure/want1
-rw-r--r--tests/test_mktag/test.tpl3
-rw-r--r--tests/test_recursive/.gitignore3
-rw-r--r--tests/test_recursive/code.php5
-rw-r--r--tests/test_recursive/test.tpl11
-rw-r--r--tests/test_recursive/want1
-rw-r--r--tests/test_scoping/.gitignore3
-rw-r--r--tests/test_scoping/code.php5
-rw-r--r--tests/test_scoping/test.tpl6
-rw-r--r--tests/test_scoping/want0
18 files changed, 321 insertions, 48 deletions
diff --git a/STECore.php b/STECore.php
index 6bde63f..add09ad 100644
--- a/STECore.php
+++ b/STECore.php
@@ -10,19 +10,18 @@ class STECore {
private $tags;
private $storage_access;
private $cur_tpl_dir;
+ private $scope;
/*
* Variables: Public variables
*
* $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 <RuntimeError> 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 <FatalRuntimeError> if a tag was called that was not registered, otherwise (default) a regular <RuntimeError> 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;
@@ -36,9 +35,10 @@ class STECore {
$this->storage_access = $storage_access;
$this->cur_tpl_dir = "/";
STEStandardLibrary::_register_lib($this);
- $this->vars = array();
$this->blockorder = array();
$this->blocks = array();
+
+ $this->set_scope(new Scope(array()));
}
/*
@@ -140,46 +140,11 @@ class STECore {
* Returns:
* A Reference to the variable.
*/
-
public function &get_var_reference($name, $create_if_not_exist) {
- $ref = &$this->_get_var_reference($this->vars, $name, $create_if_not_exist);
+ $ref = &$this->scope->get_var_reference($name, $create_if_not_exist);
return $ref;
}
- private function &_get_var_reference(&$from, $name, $create_if_not_exist) {
- $nullref = NULL;
-
- $bracket_open = strpos($name, "[");
- if($bracket_open === false) {
- if(isset($from[$name]) or $create_if_not_exist) {
- $ref = &$from[$name];
- return $ref;
- } else {
- return $nullref;
- }
- } else {
- $bracket_close = strpos($name, "]", $bracket_open);
- if($bracket_close === false) {
- throw new RuntimeError("Invalid varname \"$name\". 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) {
- $from[$varname] = array();
- } else {
- return $nullref;
- }
- }
- try {
- $ref = &$this->_get_var_reference($from[$varname], $name, $create_if_not_exist);
- return $ref;
- } catch(Exception $e) {
- throw new RuntimeError("Invalid varname \"$name\". Missing closing \"]\".");
- }
- }
- }
-
/*
* Function: set_var_by_name
* Set a template variable by its name.
@@ -193,8 +158,11 @@ class STECore {
* <RuntimeError> 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);
- $ref = $val;
+ $this->scope->set_var_by_name($name, $val);
+ }
+
+ public function set_local_var($name, $val) {
+ $this->scope->set_local_var($name, $val);
}
/*
@@ -212,8 +180,7 @@ class STECore {
* The variables value.
*/
public function get_var_by_name($name) {
- $ref = $this->_get_var_reference($this->vars, $name, false);
- return $ref === NULL ? "" : $ref;
+ return $this->scope->get_var_by_name($name);
}
/*
@@ -297,4 +264,65 @@ class STECore {
public function evalbool($txt) {
return trim($txt . "") != "";
}
+
+ public function get_scope() {
+ return $this->scope;
+ }
+
+ public function set_scope($scope) {
+ $this->scope = $scope;
+ }
+
+ public function make_closure($fx) {
+ $bound_scope = $this->scope;
+ return function() use($bound_scope, $fx) {
+ $args = func_get_args();
+ $ste = $args[0];
+
+ $prev = $ste->get_scope();
+ $scope = $bound_scope->new_subscope();
+ $ste->set_scope($scope);
+
+ try {
+ $result = call_user_func_array($fx, $args);
+ $ste->set_scope($prev);
+ return $result;
+ } catch(\Exception $e) {
+ $ste->set_scope($prev);
+ throw $e;
+ }
+ };
+ }
+
+ public function __get($name) {
+ if($name === "vars") {
+ return $this->scope;
+ }
+
+ $trace = debug_backtrace();
+ trigger_error(
+ 'Undefined property via __get(): ' . $name .
+ ' in ' . $trace[0]['file'] .
+ ' on line ' . $trace[0]['line'],
+ E_USER_NOTICE);
+ return NULL;
+ }
+
+ public function __set($name, $val) {
+ if($name !== "vars") {
+ $trace = debug_backtrace();
+ trigger_error(
+ 'Undefined property via __set(): ' . $name .
+ ' in ' . $trace[0]['file'] .
+ ' on line ' . $trace[0]['line'],
+ E_USER_NOTICE);
+ return;
+ }
+
+ if(is_array($val)) {
+ foreach($val as $k => $v) {
+ $this->scope[$k] = $v;
+ }
+ }
+ }
}
diff --git a/Scope.php b/Scope.php
new file mode 100644
index 0000000..53566ed
--- /dev/null
+++ b/Scope.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace kch42\ste;
+
+class Scope implements \ArrayAccess {
+ private $parent = NULL;
+ private $vars = array();
+
+ private static function parse_name($name) {
+ $remain = $name;
+ $fields = array();
+
+ while($remain !== "") {
+ $br_open = strpos($remain, '[');
+ if($br_open === false) {
+ $fields[] = $remain;
+ break;
+ }
+
+ $br_close = strpos($remain, ']', $br_open);
+ if($br_close === false) {
+ throw new RuntimeError("Invalid varname \"$name\". Missing closing \"]\".");
+ }
+
+ $fields[] = substr($remain, 0, $br_open);
+
+ $field = substr($remain, $br_open+1, $br_close-$br_open-1);
+ $more = substr($remain, $br_close+1);
+
+ if(strpos($field, '[') !== false) {
+ throw new RuntimeError("A variable field must not contain a '[' character.");
+ }
+
+ if((strlen($more) > 0) && ($more[0] !== '[')) {
+ // TODO: better error message, not very non-programmer friendly...
+ throw new RuntimeError("A variable name must be of format name('[' name ']')*.");
+ }
+
+ $remain = $field . $more;
+ }
+
+ return $fields;
+ }
+
+ private function &get_topvar_reference($name, $localonly) {
+ if(array_key_exists($name, $this->vars)) {
+ $ref = &$this->vars[$name];
+ return $ref;
+ }
+
+ if((!$localonly) && ($this->parent !== NULL)) {
+ $ref = &$this->parent->get_topvar_reference($name, $localonly);
+ return $ref;
+ }
+
+ throw new VarNotInScope();
+ }
+
+ /*
+ * Function: get_var_reference
+ * Get a reference to a template variable using a variable name.
+ * This can be used, if your custom tag takes a variable name as a parameter.
+ *
+ * Parameters:
+ * $name - The variables name.
+ * $create_if_not_exist - Should the variable be created, if it does not exist? Otherwise NULL will be returned, if the variable does not exist.
+ *
+ * Throws:
+ * <RuntimeError> if the variable name can not be parsed (e.g. unbalanced brackets).
+ *
+ * Returns:
+ * A Reference to the variable.
+ */
+ public function &get_var_reference($name, $create_if_not_exist, $localonly=false) {
+ $nullref = NULL;
+
+ $fields = self::parse_name($name);
+ if(count($fields) == 0) {
+ return $nullref; // TODO: or should we throw an exception here?
+ }
+
+ $first = $fields[0];
+
+ $ref = NULL;
+ try {
+ $ref = &$this->get_topvar_reference($first, $localonly);
+ } catch(VarNotInScope $e) {
+ if($create_if_not_exist) {
+ $this->vars[$first] = (count($fields) > 0) ? array() : "";
+ $ref = &$this->vars[$first];
+ } else {
+ return $nullref;
+ }
+ }
+
+ for($i = 1; $i < count($fields); $i++) {
+ $field = $fields[$i];
+
+ if(!is_array($ref)) {
+ return $nullref;
+ }
+
+ if(!array_key_exists($field, $ref)) {
+ if(!$create_if_not_exist) {
+ return $nullref;
+ }
+
+ if($i < count($fields) - 1) {
+ $ref[$field] = array();
+ } else {
+ $ref[$field] = "";
+ }
+ }
+
+ $ref = &$ref[$field];
+ }
+
+ return $ref;
+ }
+
+ /*
+ * Function: set_var_by_name
+ * Set a template variable by its name.
+ * This can be used, if your custom tag takes a variable name as a parameter.
+ *
+ * Parameters:
+ * $name - The variables name.
+ * $val - The new value.
+ *
+ * Throws:
+ * <RuntimeError> 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($name, true);
+ $ref = $val;
+ }
+
+ public function set_local_var($name, $val) {
+ $ref = &$this->get_var_reference($name, true, true);
+ $ref = $val;
+ }
+
+ /*
+ * Function: get_var_by_name
+ * Get a template variable by its name.
+ * This can be used, if your custom tag takes a variable name as a parameter.
+ *
+ * Parameters:
+ * $name - The variables name.
+ *
+ * Throws:
+ * <RuntimeError> if the variable name can not be parsed (e.g. unbalanced brackets).
+ *
+ * Returns:
+ * The variables value.
+ */
+ public function get_var_by_name($name) {
+ $ref = $this->get_var_reference($name, false);
+ return $ref === NULL ? "" : $ref;
+ }
+
+ public function new_subscope() {
+ $o = new self();
+ $o->parent = $this;
+ return $o;
+ }
+
+ /* implementing ArrayAccess */
+
+ public function offsetSet($offset, $value) {
+ $this->set_var_by_name($offset, $value);
+ }
+ public function offsetGet($offset) {
+ return $this->get_var_by_name($offset);
+ }
+ public function offsetExists($offset) {
+ try {
+ $this->get_topvar_reference($offset);
+ return true;
+ } catch(VarNotInScope $e) {
+ return false;
+ }
+ }
+ public function offsetUnset($offset) {
+ unset($this->vars[$offset]);
+ }
+}
diff --git a/Transcompiler.php b/Transcompiler.php
index 8b52fa2..b92663c 100644
--- a/Transcompiler.php
+++ b/Transcompiler.php
@@ -295,7 +295,7 @@ class Transcompiler {
throw new ParseCompileError("self::Transcompile Error: name missing in <ste:mktag>.", $ast->tpl, $ast->offset);
}
- $fxbody = "\$outputstack = array(''); \$outputstack_i = 0;\$ste->vars['_tag_parameters'] = \$params;\n";
+ $fxbody = "\$outputstack = array(''); \$outputstack_i = 0;\$ste->set_local_var('_tag_parameters', \$params);\n";
list($tagname, $tagname_pre) = self::_transcompile($ast->params["name"], true);
@@ -312,7 +312,7 @@ class Transcompiler {
$fxbody .= self::_transcompile($ast->sub);
$fxbody .= "return array_pop(\$outputstack);";
- $code .= "\$tag_fx = function(\$ste, \$params, \$sub)" . $usemandatory . "\n{\n" . self::indent_code($fxbody) . "\n};\n";
+ $code .= "\$tag_fx = \$ste->make_closure(function(\$ste, \$params, \$sub)" . $usemandatory . "\n{\n" . self::indent_code($fxbody) . "\n});\n";
$code .= $tagname_pre;
$code .= "\$ste->register_tag($tagname, \$tag_fx);\n";
@@ -394,7 +394,7 @@ class Transcompiler {
}
$code .= "\$outputstack[\$outputstack_i] .= \$ste->call_tag('" . Misc::escape_text($node->name) . "', \$${paramarray}, ";
- $code .= empty($node->sub) ? "function(\$ste) { return ''; }" : self::transcompile($node->sub);
+ $code .= empty($node->sub) ? "function(\$ste) { return ''; }" : ("\$ste->make_closure(" . self::transcompile($node->sub) . ")");
$code .= ");\n";
}
}
diff --git a/VarNotInScope.php b/VarNotInScope.php
new file mode 100644
index 0000000..179d07a
--- /dev/null
+++ b/VarNotInScope.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace kch42\ste;
+
+class VarNotInScope extends \Exception {}
diff --git a/ste.php b/ste.php
index 6647098..7e40fae 100644
--- a/ste.php
+++ b/ste.php
@@ -30,6 +30,9 @@ require_once(__DIR__ . "/Calc.php");
require_once(__DIR__ . "/Parser.php");
require_once(__DIR__ . "/Transcompiler.php");
+require_once(__DIR__ . "/VarNotInScope.php");
+require_once(__DIR__ . "/Scope.php");
+
require_once(__DIR__ . "/STEStandardLibrary.php");
require_once(__DIR__ . "/STECore.php");
diff --git a/tests/test_closure/.gitignore b/tests/test_closure/.gitignore
new file mode 100644
index 0000000..de2a41b
--- /dev/null
+++ b/tests/test_closure/.gitignore
@@ -0,0 +1,3 @@
+have
+*.ast
+*.transc.php
diff --git a/tests/test_closure/code.php b/tests/test_closure/code.php
new file mode 100644
index 0000000..de8c553
--- /dev/null
+++ b/tests/test_closure/code.php
@@ -0,0 +1,5 @@
+<?php
+
+function test_func($ste) {
+
+}
diff --git a/tests/test_closure/test.tpl b/tests/test_closure/test.tpl
new file mode 100644
index 0000000..c13bd5a
--- /dev/null
+++ b/tests/test_closure/test.tpl
@@ -0,0 +1,8 @@
+<ste:mktag name="initfoo">
+ <ste:set var="x"><ste:tagcontent /></ste:set>
+ <ste:mktag name="foo">
+ <ste:calc>$x + <ste:tagcontent /></ste:calc>
+ </ste:mktag>
+</ste:mktag>
+<ste:initfoo>10</ste:initfoo>
+<ste:foo>20</ste:foo> \ No newline at end of file
diff --git a/tests/test_closure/want b/tests/test_closure/want
new file mode 100644
index 0000000..64bb6b7
--- /dev/null
+++ b/tests/test_closure/want
@@ -0,0 +1 @@
+30
diff --git a/tests/test_mktag/test.tpl b/tests/test_mktag/test.tpl
index c381ef4..c446206 100644
--- a/tests/test_mktag/test.tpl
+++ b/tests/test_mktag/test.tpl
@@ -1,11 +1,10 @@
<ste:mktag name="double"><ste:calc>2 * <ste:tagcontent /></ste:calc></ste:mktag>
<ste:mktag name="foo" mandatory="a|b">
- <ste:set var="b">$_tag_parameters[b]</ste:set><ste:comment>Ugly hack, since STE does no scoping...</ste:comment>
<ste:for counter="i" start="0" stop="$_tag_parameters[a]">
<ste:if>
<ste:even>$i</ste:even>
<ste:then><ste:double>$i</ste:double></ste:then>
- <ste:else>$b</ste:else>
+ <ste:else>$_tag_parameters[b]</ste:else>
</ste:if>
</ste:for>
</ste:mktag>
diff --git a/tests/test_recursive/.gitignore b/tests/test_recursive/.gitignore
new file mode 100644
index 0000000..de2a41b
--- /dev/null
+++ b/tests/test_recursive/.gitignore
@@ -0,0 +1,3 @@
+have
+*.ast
+*.transc.php
diff --git a/tests/test_recursive/code.php b/tests/test_recursive/code.php
new file mode 100644
index 0000000..4ca3852
--- /dev/null
+++ b/tests/test_recursive/code.php
@@ -0,0 +1,5 @@
+<?php
+
+function test_func($ste) {
+ $ste->mute_runtime_errors = false;
+}
diff --git a/tests/test_recursive/test.tpl b/tests/test_recursive/test.tpl
new file mode 100644
index 0000000..bea3934
--- /dev/null
+++ b/tests/test_recursive/test.tpl
@@ -0,0 +1,11 @@
+<ste:mktag name="fac" mandatory="n">
+ <ste:if>
+ ~{$_tag_parameters[n]|eq|0}
+ <ste:then>1</ste:then>
+ <ste:else>
+ <ste:set var="nextn"><ste:calc>$_tag_parameters[n] - 1</ste:calc></ste:set>
+ <ste:calc><ste:fac n="$nextn" /> * $_tag_parameters[n]</ste:calc>
+ </ste:else>
+ </ste:if>
+</ste:mktag>
+<ste:fac n="10" /> \ No newline at end of file
diff --git a/tests/test_recursive/want b/tests/test_recursive/want
new file mode 100644
index 0000000..3fbd4a8
--- /dev/null
+++ b/tests/test_recursive/want
@@ -0,0 +1 @@
+3628800
diff --git a/tests/test_scoping/.gitignore b/tests/test_scoping/.gitignore
new file mode 100644
index 0000000..de2a41b
--- /dev/null
+++ b/tests/test_scoping/.gitignore
@@ -0,0 +1,3 @@
+have
+*.ast
+*.transc.php
diff --git a/tests/test_scoping/code.php b/tests/test_scoping/code.php
new file mode 100644
index 0000000..de8c553
--- /dev/null
+++ b/tests/test_scoping/code.php
@@ -0,0 +1,5 @@
+<?php
+
+function test_func($ste) {
+
+}
diff --git a/tests/test_scoping/test.tpl b/tests/test_scoping/test.tpl
new file mode 100644
index 0000000..25a46e7
--- /dev/null
+++ b/tests/test_scoping/test.tpl
@@ -0,0 +1,6 @@
+<ste:mktag name="foo">
+ <ste:set var="bla">!</ste:set>
+</ste:mktag>
+
+<ste:foo />
+$bla \ No newline at end of file
diff --git a/tests/test_scoping/want b/tests/test_scoping/want
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/test_scoping/want