Merge branch 'ssviewer-rewrite'

This commit is contained in:
Hamish Friedlander 2011-03-10 17:52:10 +13:00
commit f5b867a35d
21 changed files with 7195 additions and 442 deletions

3778
core/SSTemplateParser.php Normal file
View File

@ -0,0 +1,3778 @@
<?php
/*
WARNING: This file has been machine generated. Do not edit it, or your changes will be overwritten next time it is compiled.
*/
// We want this to work when run by hand too
if (defined(THIRDPARTY_PATH)) {
require THIRDPARTY_PATH . '/php-peg/Parser.php' ;
}
else {
$base = dirname(__FILE__);
require $base.'/../thirdparty/php-peg/Parser.php';
}
/**
This is the exception raised when failing to parse a template. Note that we don't currently do any static analysis, so we can't know
if the template will run, just if it's malformed. It also won't catch mistakes that still look valid.
*/
class SSTemplateParseException extends Exception {
function __construct($message, $parser) {
$prior = substr($parser->string, 0, $parser->pos);
preg_match_all('/\r\n|\r|\n/', $prior, $matches);
$line = count($matches[0])+1;
parent::__construct("Parse error in template on line $line. Error was: $message");
}
}
/**
This is the parser for the SilverStripe template language. It gets called on a string and uses a php-peg parser to match
that string against the language structure, building up the PHP code to execute that structure as it parses
The $result array that is built up as part of the parsing (see thirdparty/php-peg/README.md for more on how parsers
build results) has one special member, 'php', which contains the php equivalent of that part of the template tree.
Some match rules generate alternate php, or other variations, so check the per-match documentation too.
Terms used:
Marked: A string or lookup in the template that has been explictly marked as such - lookups by prepending with "$"
(like $Foo.Bar), strings by wrapping with single or double quotes ('Foo' or "Foo")
Bare: The opposite of marked. An argument that has to has it's type inferred by usage and 2.4 defaults.
Example of using a bare argument for a loop block: <% loop Foo %>
Block: One of two SS template structures. The special characters "<%" and "%>" are used to wrap the opening and
(required or forbidden depending on which block exactly) closing block marks.
Open Block: An SS template block that doesn't wrap any content or have a closing end tag (in fact, a closing end tag is
forbidden)
Closed Block: An SS template block that wraps content, and requires a counterpart <% end_blockname %> tag
*/
class SSTemplateParser extends Parser {
/**
* @var bool - Set true by SSTemplateParser::compileString if the template should include comments intended
* for debugging (template source, included files, etc)
*/
protected $includeDebuggingComments = false;
/**
* Override the function that constructs the result arrays to also prepare a 'php' item in the array
*/
function construct($matchrule, $name, $arguments = null) {
$res = parent::construct($matchrule, $name, $arguments);
if (!isset($res['php'])) $res['php'] = '';
return $res;
}
/* Template: (Comment | If | Require | CacheBlock | UncachedBlock | OldI18NTag | ClosedBlock | OpenBlock | MalformedBlock | Injection | Text)+ */
protected $match_Template_typestack = array('Template');
function match_Template ($stack = array()) {
$matchrule = "Template"; $result = $this->construct($matchrule, $matchrule, null);
$count = 0;
while (true) {
$res_42 = $result;
$pos_42 = $this->pos;
$_41 = NULL;
do {
$_39 = NULL;
do {
$res_0 = $result;
$pos_0 = $this->pos;
$matcher = 'match_'.'Comment'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_39 = TRUE; break;
}
$result = $res_0;
$this->pos = $pos_0;
$_37 = NULL;
do {
$res_2 = $result;
$pos_2 = $this->pos;
$matcher = 'match_'.'If'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_37 = TRUE; break;
}
$result = $res_2;
$this->pos = $pos_2;
$_35 = NULL;
do {
$res_4 = $result;
$pos_4 = $this->pos;
$matcher = 'match_'.'Require'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_35 = TRUE; break;
}
$result = $res_4;
$this->pos = $pos_4;
$_33 = NULL;
do {
$res_6 = $result;
$pos_6 = $this->pos;
$matcher = 'match_'.'CacheBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_33 = TRUE; break;
}
$result = $res_6;
$this->pos = $pos_6;
$_31 = NULL;
do {
$res_8 = $result;
$pos_8 = $this->pos;
$matcher = 'match_'.'UncachedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_31 = TRUE; break;
}
$result = $res_8;
$this->pos = $pos_8;
$_29 = NULL;
do {
$res_10 = $result;
$pos_10 = $this->pos;
$matcher = 'match_'.'OldI18NTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_29 = TRUE; break;
}
$result = $res_10;
$this->pos = $pos_10;
$_27 = NULL;
do {
$res_12 = $result;
$pos_12 = $this->pos;
$matcher = 'match_'.'ClosedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_27 = TRUE; break;
}
$result = $res_12;
$this->pos = $pos_12;
$_25 = NULL;
do {
$res_14 = $result;
$pos_14 = $this->pos;
$matcher = 'match_'.'OpenBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_25 = TRUE; break;
}
$result = $res_14;
$this->pos = $pos_14;
$_23 = NULL;
do {
$res_16 = $result;
$pos_16 = $this->pos;
$matcher = 'match_'.'MalformedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_23 = TRUE; break;
}
$result = $res_16;
$this->pos = $pos_16;
$_21 = NULL;
do {
$res_18 = $result;
$pos_18 = $this->pos;
$matcher = 'match_'.'Injection'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_21 = TRUE; break;
}
$result = $res_18;
$this->pos = $pos_18;
$matcher = 'match_'.'Text'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_21 = TRUE; break;
}
$result = $res_18;
$this->pos = $pos_18;
$_21 = FALSE; break;
}
while(0);
if( $_21 === TRUE ) { $_23 = TRUE; break; }
$result = $res_16;
$this->pos = $pos_16;
$_23 = FALSE; break;
}
while(0);
if( $_23 === TRUE ) { $_25 = TRUE; break; }
$result = $res_14;
$this->pos = $pos_14;
$_25 = FALSE; break;
}
while(0);
if( $_25 === TRUE ) { $_27 = TRUE; break; }
$result = $res_12;
$this->pos = $pos_12;
$_27 = FALSE; break;
}
while(0);
if( $_27 === TRUE ) { $_29 = TRUE; break; }
$result = $res_10;
$this->pos = $pos_10;
$_29 = FALSE; break;
}
while(0);
if( $_29 === TRUE ) { $_31 = TRUE; break; }
$result = $res_8;
$this->pos = $pos_8;
$_31 = FALSE; break;
}
while(0);
if( $_31 === TRUE ) { $_33 = TRUE; break; }
$result = $res_6;
$this->pos = $pos_6;
$_33 = FALSE; break;
}
while(0);
if( $_33 === TRUE ) { $_35 = TRUE; break; }
$result = $res_4;
$this->pos = $pos_4;
$_35 = FALSE; break;
}
while(0);
if( $_35 === TRUE ) { $_37 = TRUE; break; }
$result = $res_2;
$this->pos = $pos_2;
$_37 = FALSE; break;
}
while(0);
if( $_37 === TRUE ) { $_39 = TRUE; break; }
$result = $res_0;
$this->pos = $pos_0;
$_39 = FALSE; break;
}
while(0);
if( $_39 === FALSE) { $_41 = FALSE; break; }
$_41 = TRUE; break;
}
while(0);
if( $_41 === FALSE) {
$result = $res_42;
$this->pos = $pos_42;
unset( $res_42 );
unset( $pos_42 );
break;
}
$count += 1;
}
if ($count > 0) { return $this->finalise($result); }
else { return FALSE; }
}
function Template_STR(&$res, $sub) {
$res['php'] .= $sub['php'] . PHP_EOL ;
}
/* Word: / [A-Za-z_] [A-Za-z0-9_]* / */
protected $match_Word_typestack = array('Word');
function match_Word ($stack = array()) {
$matchrule = "Word"; $result = $this->construct($matchrule, $matchrule, null);
if (( $subres = $this->rx( '/ [A-Za-z_] [A-Za-z0-9_]* /' ) ) !== FALSE) {
$result["text"] .= $subres;
return $this->finalise($result);
}
else { return FALSE; }
}
/* Number: / [0-9]+ / */
protected $match_Number_typestack = array('Number');
function match_Number ($stack = array()) {
$matchrule = "Number"; $result = $this->construct($matchrule, $matchrule, null);
if (( $subres = $this->rx( '/ [0-9]+ /' ) ) !== FALSE) {
$result["text"] .= $subres;
return $this->finalise($result);
}
else { return FALSE; }
}
/* Value: / [A-Za-z0-9_]+ / */
protected $match_Value_typestack = array('Value');
function match_Value ($stack = array()) {
$matchrule = "Value"; $result = $this->construct($matchrule, $matchrule, null);
if (( $subres = $this->rx( '/ [A-Za-z0-9_]+ /' ) ) !== FALSE) {
$result["text"] .= $subres;
return $this->finalise($result);
}
else { return FALSE; }
}
/* CallArguments: :Argument ( < "," < :Argument )* */
protected $match_CallArguments_typestack = array('CallArguments');
function match_CallArguments ($stack = array()) {
$matchrule = "CallArguments"; $result = $this->construct($matchrule, $matchrule, null);
$_53 = NULL;
do {
$matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Argument" );
}
else { $_53 = FALSE; break; }
while (true) {
$res_52 = $result;
$pos_52 = $this->pos;
$_51 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ',') {
$this->pos += 1;
$result["text"] .= ',';
}
else { $_51 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Argument" );
}
else { $_51 = FALSE; break; }
$_51 = TRUE; break;
}
while(0);
if( $_51 === FALSE) {
$result = $res_52;
$this->pos = $pos_52;
unset( $res_52 );
unset( $pos_52 );
break;
}
}
$_53 = TRUE; break;
}
while(0);
if( $_53 === TRUE ) { return $this->finalise($result); }
if( $_53 === FALSE) { return FALSE; }
}
/**
* Values are bare words in templates, but strings in PHP. We rely on PHP's type conversion to back-convert strings
* to numbers when needed.
*/
function CallArguments_Argument(&$res, $sub) {
if (!empty($res['php'])) $res['php'] .= ', ';
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : str_replace('$$FINAL', 'XML_val', $sub['php']);
}
/* Call: Method:Word ( "(" < :CallArguments? > ")" )? */
protected $match_Call_typestack = array('Call');
function match_Call ($stack = array()) {
$matchrule = "Call"; $result = $this->construct($matchrule, $matchrule, null);
$_63 = NULL;
do {
$matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Method" );
}
else { $_63 = FALSE; break; }
$res_62 = $result;
$pos_62 = $this->pos;
$_61 = NULL;
do {
if (substr($this->string,$this->pos,1) == '(') {
$this->pos += 1;
$result["text"] .= '(';
}
else { $_61 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$res_58 = $result;
$pos_58 = $this->pos;
$matcher = 'match_'.'CallArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "CallArguments" );
}
else {
$result = $res_58;
$this->pos = $pos_58;
unset( $res_58 );
unset( $pos_58 );
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ')') {
$this->pos += 1;
$result["text"] .= ')';
}
else { $_61 = FALSE; break; }
$_61 = TRUE; break;
}
while(0);
if( $_61 === FALSE) {
$result = $res_62;
$this->pos = $pos_62;
unset( $res_62 );
unset( $pos_62 );
}
$_63 = TRUE; break;
}
while(0);
if( $_63 === TRUE ) { return $this->finalise($result); }
if( $_63 === FALSE) { return FALSE; }
}
/* LookupStep: :Call &"." */
protected $match_LookupStep_typestack = array('LookupStep');
function match_LookupStep ($stack = array()) {
$matchrule = "LookupStep"; $result = $this->construct($matchrule, $matchrule, null);
$_67 = NULL;
do {
$matcher = 'match_'.'Call'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Call" );
}
else { $_67 = FALSE; break; }
$res_66 = $result;
$pos_66 = $this->pos;
if (substr($this->string,$this->pos,1) == '.') {
$this->pos += 1;
$result["text"] .= '.';
$result = $res_66;
$this->pos = $pos_66;
}
else {
$result = $res_66;
$this->pos = $pos_66;
$_67 = FALSE; break;
}
$_67 = TRUE; break;
}
while(0);
if( $_67 === TRUE ) { return $this->finalise($result); }
if( $_67 === FALSE) { return FALSE; }
}
/* LastLookupStep: :Call */
protected $match_LastLookupStep_typestack = array('LastLookupStep');
function match_LastLookupStep ($stack = array()) {
$matchrule = "LastLookupStep"; $result = $this->construct($matchrule, $matchrule, null);
$matcher = 'match_'.'Call'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Call" );
return $this->finalise($result);
}
else { return FALSE; }
}
/* Lookup: LookupStep ("." LookupStep)* "." LastLookupStep | LastLookupStep */
protected $match_Lookup_typestack = array('Lookup');
function match_Lookup ($stack = array()) {
$matchrule = "Lookup"; $result = $this->construct($matchrule, $matchrule, null);
$_81 = NULL;
do {
$res_70 = $result;
$pos_70 = $this->pos;
$_78 = NULL;
do {
$matcher = 'match_'.'LookupStep'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_78 = FALSE; break; }
while (true) {
$res_75 = $result;
$pos_75 = $this->pos;
$_74 = NULL;
do {
if (substr($this->string,$this->pos,1) == '.') {
$this->pos += 1;
$result["text"] .= '.';
}
else { $_74 = FALSE; break; }
$matcher = 'match_'.'LookupStep'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
}
else { $_74 = FALSE; break; }
$_74 = TRUE; break;
}
while(0);
if( $_74 === FALSE) {
$result = $res_75;
$this->pos = $pos_75;
unset( $res_75 );
unset( $pos_75 );
break;
}
}
if (substr($this->string,$this->pos,1) == '.') {
$this->pos += 1;
$result["text"] .= '.';
}
else { $_78 = FALSE; break; }
$matcher = 'match_'.'LastLookupStep'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_78 = FALSE; break; }
$_78 = TRUE; break;
}
while(0);
if( $_78 === TRUE ) { $_81 = TRUE; break; }
$result = $res_70;
$this->pos = $pos_70;
$matcher = 'match_'.'LastLookupStep'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_81 = TRUE; break;
}
$result = $res_70;
$this->pos = $pos_70;
$_81 = FALSE; break;
}
while(0);
if( $_81 === TRUE ) { return $this->finalise($result); }
if( $_81 === FALSE) { return FALSE; }
}
function Lookup__construct(&$res) {
$res['php'] = '$scope';
$res['LookupSteps'] = array();
}
/**
* The basic generated PHP of LookupStep and LastLookupStep is the same, except that LookupStep calls 'obj' to
* get the next ViewableData in the sequence, and LastLookupStep calls different methods (XML_val, hasValue, obj)
* depending on the context the lookup is used in.
*/
function Lookup_AddLookupStep(&$res, $sub, $method) {
$res['LookupSteps'][] = $sub;
$property = $sub['Call']['Method']['text'];
if (isset($sub['Call']['CallArguments']) && $arguments = $sub['Call']['CallArguments']['php']) {
$res['php'] .= "->$method('$property', array($arguments), true)";
}
else {
$res['php'] .= "->$method('$property', null, true)";
}
}
function Lookup_LookupStep(&$res, $sub) {
$this->Lookup_AddLookupStep($res, $sub, 'obj');
}
function Lookup_LastLookupStep(&$res, $sub) {
$this->Lookup_AddLookupStep($res, $sub, '$$FINAL');
}
/* SimpleInjection: '$' :Lookup */
protected $match_SimpleInjection_typestack = array('SimpleInjection');
function match_SimpleInjection ($stack = array()) {
$matchrule = "SimpleInjection"; $result = $this->construct($matchrule, $matchrule, null);
$_85 = NULL;
do {
if (substr($this->string,$this->pos,1) == '$') {
$this->pos += 1;
$result["text"] .= '$';
}
else { $_85 = FALSE; break; }
$matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Lookup" );
}
else { $_85 = FALSE; break; }
$_85 = TRUE; break;
}
while(0);
if( $_85 === TRUE ) { return $this->finalise($result); }
if( $_85 === FALSE) { return FALSE; }
}
/* BracketInjection: '{$' :Lookup "}" */
protected $match_BracketInjection_typestack = array('BracketInjection');
function match_BracketInjection ($stack = array()) {
$matchrule = "BracketInjection"; $result = $this->construct($matchrule, $matchrule, null);
$_90 = NULL;
do {
if (( $subres = $this->literal( '{$' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_90 = FALSE; break; }
$matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Lookup" );
}
else { $_90 = FALSE; break; }
if (substr($this->string,$this->pos,1) == '}') {
$this->pos += 1;
$result["text"] .= '}';
}
else { $_90 = FALSE; break; }
$_90 = TRUE; break;
}
while(0);
if( $_90 === TRUE ) { return $this->finalise($result); }
if( $_90 === FALSE) { return FALSE; }
}
/* Injection: BracketInjection | SimpleInjection */
protected $match_Injection_typestack = array('Injection');
function match_Injection ($stack = array()) {
$matchrule = "Injection"; $result = $this->construct($matchrule, $matchrule, null);
$_95 = NULL;
do {
$res_92 = $result;
$pos_92 = $this->pos;
$matcher = 'match_'.'BracketInjection'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_95 = TRUE; break;
}
$result = $res_92;
$this->pos = $pos_92;
$matcher = 'match_'.'SimpleInjection'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_95 = TRUE; break;
}
$result = $res_92;
$this->pos = $pos_92;
$_95 = FALSE; break;
}
while(0);
if( $_95 === TRUE ) { return $this->finalise($result); }
if( $_95 === FALSE) { return FALSE; }
}
function Injection_STR(&$res, $sub) {
$res['php'] = '$val .= '. str_replace('$$FINAL', 'XML_val', $sub['Lookup']['php']) . ';';
}
/* DollarMarkedLookup: SimpleInjection */
protected $match_DollarMarkedLookup_typestack = array('DollarMarkedLookup');
function match_DollarMarkedLookup ($stack = array()) {
$matchrule = "DollarMarkedLookup"; $result = $this->construct($matchrule, $matchrule, null);
$matcher = 'match_'.'SimpleInjection'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
return $this->finalise($result);
}
else { return FALSE; }
}
function DollarMarkedLookup_STR(&$res, $sub) {
$res['Lookup'] = $sub['Lookup'];
}
/* QuotedString: q:/['"]/ String:/ (\\\\ | \\. | [^$q\\])* / '$q' */
protected $match_QuotedString_typestack = array('QuotedString');
function match_QuotedString ($stack = array()) {
$matchrule = "QuotedString"; $result = $this->construct($matchrule, $matchrule, null);
$_101 = NULL;
do {
$stack[] = $result; $result = $this->construct( $matchrule, "q" );
if (( $subres = $this->rx( '/[\'"]/' ) ) !== FALSE) {
$result["text"] .= $subres;
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'q' );
}
else {
$result = array_pop($stack);
$_101 = FALSE; break;
}
$stack[] = $result; $result = $this->construct( $matchrule, "String" );
if (( $subres = $this->rx( '/ (\\\\\\\\ | \\\\. | [^'.$this->expression($result, $stack, 'q').'\\\\])* /' ) ) !== FALSE) {
$result["text"] .= $subres;
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'String' );
}
else {
$result = array_pop($stack);
$_101 = FALSE; break;
}
if (( $subres = $this->literal( ''.$this->expression($result, $stack, 'q').'' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_101 = FALSE; break; }
$_101 = TRUE; break;
}
while(0);
if( $_101 === TRUE ) { return $this->finalise($result); }
if( $_101 === FALSE) { return FALSE; }
}
/* FreeString: /[^,)%!=|&]+/ */
protected $match_FreeString_typestack = array('FreeString');
function match_FreeString ($stack = array()) {
$matchrule = "FreeString"; $result = $this->construct($matchrule, $matchrule, null);
if (( $subres = $this->rx( '/[^,)%!=|&]+/' ) ) !== FALSE) {
$result["text"] .= $subres;
return $this->finalise($result);
}
else { return FALSE; }
}
/* Argument:
:DollarMarkedLookup |
:QuotedString |
:Lookup !(< FreeString)|
:FreeString */
protected $match_Argument_typestack = array('Argument');
function match_Argument ($stack = array()) {
$matchrule = "Argument"; $result = $this->construct($matchrule, $matchrule, null);
$_121 = NULL;
do {
$res_104 = $result;
$pos_104 = $this->pos;
$matcher = 'match_'.'DollarMarkedLookup'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "DollarMarkedLookup" );
$_121 = TRUE; break;
}
$result = $res_104;
$this->pos = $pos_104;
$_119 = NULL;
do {
$res_106 = $result;
$pos_106 = $this->pos;
$matcher = 'match_'.'QuotedString'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "QuotedString" );
$_119 = TRUE; break;
}
$result = $res_106;
$this->pos = $pos_106;
$_117 = NULL;
do {
$res_108 = $result;
$pos_108 = $this->pos;
$_114 = NULL;
do {
$matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Lookup" );
}
else { $_114 = FALSE; break; }
$res_113 = $result;
$pos_113 = $this->pos;
$_112 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'FreeString'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
}
else { $_112 = FALSE; break; }
$_112 = TRUE; break;
}
while(0);
if( $_112 === TRUE ) {
$result = $res_113;
$this->pos = $pos_113;
$_114 = FALSE; break;
}
if( $_112 === FALSE) {
$result = $res_113;
$this->pos = $pos_113;
}
$_114 = TRUE; break;
}
while(0);
if( $_114 === TRUE ) { $_117 = TRUE; break; }
$result = $res_108;
$this->pos = $pos_108;
$matcher = 'match_'.'FreeString'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "FreeString" );
$_117 = TRUE; break;
}
$result = $res_108;
$this->pos = $pos_108;
$_117 = FALSE; break;
}
while(0);
if( $_117 === TRUE ) { $_119 = TRUE; break; }
$result = $res_106;
$this->pos = $pos_106;
$_119 = FALSE; break;
}
while(0);
if( $_119 === TRUE ) { $_121 = TRUE; break; }
$result = $res_104;
$this->pos = $pos_104;
$_121 = FALSE; break;
}
while(0);
if( $_121 === TRUE ) { return $this->finalise($result); }
if( $_121 === FALSE) { return FALSE; }
}
/**
* If we get a bare value, we don't know enough to determine exactly what php would be the translation, because
* we don't know if the position of use indicates a lookup or a string argument.
*
* Instead, we record 'ArgumentMode' as a member of this matches results node, which can be:
* - lookup if this argument was unambiguously a lookup (marked as such)
* - string is this argument was unambiguously a string (marked as such, or impossible to parse as lookup)
* - default if this argument needs to be handled as per 2.4
*
* In the case of 'default', there is no php member of the results node, but instead 'lookup_php', which
* should be used by the parent if the context indicates a lookup, and 'string_php' which should be used
* if the context indicates a string
*/
function Argument_DollarMarkedLookup(&$res, $sub) {
$res['ArgumentMode'] = 'lookup';
$res['php'] = $sub['Lookup']['php'];
}
function Argument_QuotedString(&$res, $sub) {
$res['ArgumentMode'] = 'string';
$res['php'] = "'" . str_replace("'", "\\'", $sub['String']['text']) . "'";
}
function Argument_Lookup(&$res, $sub) {
if (count($sub['LookupSteps']) == 1 && !isset($sub['LookupSteps'][0]['Call']['Arguments'])) {
$res['ArgumentMode'] = 'default';
$res['lookup_php'] = $sub['php'];
$res['string_php'] = "'".$sub['LookupSteps'][0]['Call']['Method']['text']."'";
}
else {
$res['ArgumentMode'] = 'lookup';
$res['php'] = $sub['php'];
}
}
function Argument_FreeString(&$res, $sub) {
$res['ArgumentMode'] = 'string';
$res['php'] = "'" . str_replace("'", "\\'", $sub['text']) . "'";
}
/* ComparisonOperator: "==" | "!=" | "=" */
protected $match_ComparisonOperator_typestack = array('ComparisonOperator');
function match_ComparisonOperator ($stack = array()) {
$matchrule = "ComparisonOperator"; $result = $this->construct($matchrule, $matchrule, null);
$_130 = NULL;
do {
$res_123 = $result;
$pos_123 = $this->pos;
if (( $subres = $this->literal( '==' ) ) !== FALSE) {
$result["text"] .= $subres;
$_130 = TRUE; break;
}
$result = $res_123;
$this->pos = $pos_123;
$_128 = NULL;
do {
$res_125 = $result;
$pos_125 = $this->pos;
if (( $subres = $this->literal( '!=' ) ) !== FALSE) {
$result["text"] .= $subres;
$_128 = TRUE; break;
}
$result = $res_125;
$this->pos = $pos_125;
if (substr($this->string,$this->pos,1) == '=') {
$this->pos += 1;
$result["text"] .= '=';
$_128 = TRUE; break;
}
$result = $res_125;
$this->pos = $pos_125;
$_128 = FALSE; break;
}
while(0);
if( $_128 === TRUE ) { $_130 = TRUE; break; }
$result = $res_123;
$this->pos = $pos_123;
$_130 = FALSE; break;
}
while(0);
if( $_130 === TRUE ) { return $this->finalise($result); }
if( $_130 === FALSE) { return FALSE; }
}
/* Comparison: Argument < ComparisonOperator > Argument */
protected $match_Comparison_typestack = array('Comparison');
function match_Comparison ($stack = array()) {
$matchrule = "Comparison"; $result = $this->construct($matchrule, $matchrule, null);
$_137 = NULL;
do {
$matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_137 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'ComparisonOperator'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_137 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_137 = FALSE; break; }
$_137 = TRUE; break;
}
while(0);
if( $_137 === TRUE ) { return $this->finalise($result); }
if( $_137 === FALSE) { return FALSE; }
}
function Comparison_Argument(&$res, $sub) {
if ($sub['ArgumentMode'] == 'default') {
if (!empty($res['php'])) $res['php'] .= $sub['string_php'];
else $res['php'] = str_replace('$$FINAL', 'XML_val', $sub['lookup_php']);
}
else {
$res['php'] .= str_replace('$$FINAL', 'XML_val', $sub['php']);
}
}
function Comparison_ComparisonOperator(&$res, $sub) {
$res['php'] .= ($sub['text'] == '=' ? '==' : $sub['text']);
}
/* PresenceCheck: (Not:'not' <)? Argument */
protected $match_PresenceCheck_typestack = array('PresenceCheck');
function match_PresenceCheck ($stack = array()) {
$matchrule = "PresenceCheck"; $result = $this->construct($matchrule, $matchrule, null);
$_144 = NULL;
do {
$res_142 = $result;
$pos_142 = $this->pos;
$_141 = NULL;
do {
$stack[] = $result; $result = $this->construct( $matchrule, "Not" );
if (( $subres = $this->literal( 'not' ) ) !== FALSE) {
$result["text"] .= $subres;
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'Not' );
}
else {
$result = array_pop($stack);
$_141 = FALSE; break;
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$_141 = TRUE; break;
}
while(0);
if( $_141 === FALSE) {
$result = $res_142;
$this->pos = $pos_142;
unset( $res_142 );
unset( $pos_142 );
}
$matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_144 = FALSE; break; }
$_144 = TRUE; break;
}
while(0);
if( $_144 === TRUE ) { return $this->finalise($result); }
if( $_144 === FALSE) { return FALSE; }
}
function PresenceCheck_Not(&$res, $sub) {
$res['php'] = '!';
}
function PresenceCheck_Argument(&$res, $sub) {
if ($sub['ArgumentMode'] == 'string') {
$res['php'] .= '((bool)'.$sub['php'].')';
}
else {
$php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']);
// TODO: kinda hacky - maybe we need a way to pass state down the parse chain so
// Lookup_LastLookupStep and Argument_BareWord can produce hasValue instead of XML_val
$res['php'] .= str_replace('$$FINAL', 'hasValue', $php);
}
}
/* IfArgumentPortion: Comparison | PresenceCheck */
protected $match_IfArgumentPortion_typestack = array('IfArgumentPortion');
function match_IfArgumentPortion ($stack = array()) {
$matchrule = "IfArgumentPortion"; $result = $this->construct($matchrule, $matchrule, null);
$_149 = NULL;
do {
$res_146 = $result;
$pos_146 = $this->pos;
$matcher = 'match_'.'Comparison'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_149 = TRUE; break;
}
$result = $res_146;
$this->pos = $pos_146;
$matcher = 'match_'.'PresenceCheck'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_149 = TRUE; break;
}
$result = $res_146;
$this->pos = $pos_146;
$_149 = FALSE; break;
}
while(0);
if( $_149 === TRUE ) { return $this->finalise($result); }
if( $_149 === FALSE) { return FALSE; }
}
function IfArgumentPortion_STR(&$res, $sub) {
$res['php'] = $sub['php'];
}
/* BooleanOperator: "||" | "&&" */
protected $match_BooleanOperator_typestack = array('BooleanOperator');
function match_BooleanOperator ($stack = array()) {
$matchrule = "BooleanOperator"; $result = $this->construct($matchrule, $matchrule, null);
$_154 = NULL;
do {
$res_151 = $result;
$pos_151 = $this->pos;
if (( $subres = $this->literal( '||' ) ) !== FALSE) {
$result["text"] .= $subres;
$_154 = TRUE; break;
}
$result = $res_151;
$this->pos = $pos_151;
if (( $subres = $this->literal( '&&' ) ) !== FALSE) {
$result["text"] .= $subres;
$_154 = TRUE; break;
}
$result = $res_151;
$this->pos = $pos_151;
$_154 = FALSE; break;
}
while(0);
if( $_154 === TRUE ) { return $this->finalise($result); }
if( $_154 === FALSE) { return FALSE; }
}
/* IfArgument: :IfArgumentPortion ( < :BooleanOperator < :IfArgumentPortion )* */
protected $match_IfArgument_typestack = array('IfArgument');
function match_IfArgument ($stack = array()) {
$matchrule = "IfArgument"; $result = $this->construct($matchrule, $matchrule, null);
$_163 = NULL;
do {
$matcher = 'match_'.'IfArgumentPortion'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "IfArgumentPortion" );
}
else { $_163 = FALSE; break; }
while (true) {
$res_162 = $result;
$pos_162 = $this->pos;
$_161 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'BooleanOperator'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "BooleanOperator" );
}
else { $_161 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'IfArgumentPortion'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "IfArgumentPortion" );
}
else { $_161 = FALSE; break; }
$_161 = TRUE; break;
}
while(0);
if( $_161 === FALSE) {
$result = $res_162;
$this->pos = $pos_162;
unset( $res_162 );
unset( $pos_162 );
break;
}
}
$_163 = TRUE; break;
}
while(0);
if( $_163 === TRUE ) { return $this->finalise($result); }
if( $_163 === FALSE) { return FALSE; }
}
function IfArgument_IfArgumentPortion(&$res, $sub) {
$res['php'] .= $sub['php'];
}
function IfArgument_BooleanOperator(&$res, $sub) {
$res['php'] .= $sub['text'];
}
/* IfPart: '<%' < 'if' [ :IfArgument > '%>' Template:$TemplateMatcher? */
protected $match_IfPart_typestack = array('IfPart');
function match_IfPart ($stack = array()) {
$matchrule = "IfPart"; $result = $this->construct($matchrule, $matchrule, null);
$_173 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_173 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'if' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_173 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_173 = FALSE; break; }
$matcher = 'match_'.'IfArgument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "IfArgument" );
}
else { $_173 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_173 = FALSE; break; }
$res_172 = $result;
$pos_172 = $this->pos;
$matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Template" );
}
else {
$result = $res_172;
$this->pos = $pos_172;
unset( $res_172 );
unset( $pos_172 );
}
$_173 = TRUE; break;
}
while(0);
if( $_173 === TRUE ) { return $this->finalise($result); }
if( $_173 === FALSE) { return FALSE; }
}
/* ElseIfPart: '<%' < 'else_if' [ :IfArgument > '%>' Template:$TemplateMatcher */
protected $match_ElseIfPart_typestack = array('ElseIfPart');
function match_ElseIfPart ($stack = array()) {
$matchrule = "ElseIfPart"; $result = $this->construct($matchrule, $matchrule, null);
$_183 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_183 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'else_if' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_183 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_183 = FALSE; break; }
$matcher = 'match_'.'IfArgument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "IfArgument" );
}
else { $_183 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_183 = FALSE; break; }
$matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Template" );
}
else { $_183 = FALSE; break; }
$_183 = TRUE; break;
}
while(0);
if( $_183 === TRUE ) { return $this->finalise($result); }
if( $_183 === FALSE) { return FALSE; }
}
/* ElsePart: '<%' < 'else' > '%>' Template:$TemplateMatcher */
protected $match_ElsePart_typestack = array('ElsePart');
function match_ElsePart ($stack = array()) {
$matchrule = "ElsePart"; $result = $this->construct($matchrule, $matchrule, null);
$_191 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_191 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'else' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_191 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_191 = FALSE; break; }
$matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Template" );
}
else { $_191 = FALSE; break; }
$_191 = TRUE; break;
}
while(0);
if( $_191 === TRUE ) { return $this->finalise($result); }
if( $_191 === FALSE) { return FALSE; }
}
/* If: IfPart ElseIfPart* ElsePart? '<%' < 'end_if' > '%>' */
protected $match_If_typestack = array('If');
function match_If ($stack = array()) {
$matchrule = "If"; $result = $this->construct($matchrule, $matchrule, null);
$_201 = NULL;
do {
$matcher = 'match_'.'IfPart'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_201 = FALSE; break; }
while (true) {
$res_194 = $result;
$pos_194 = $this->pos;
$matcher = 'match_'.'ElseIfPart'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else {
$result = $res_194;
$this->pos = $pos_194;
unset( $res_194 );
unset( $pos_194 );
break;
}
}
$res_195 = $result;
$pos_195 = $this->pos;
$matcher = 'match_'.'ElsePart'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else {
$result = $res_195;
$this->pos = $pos_195;
unset( $res_195 );
unset( $pos_195 );
}
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_201 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'end_if' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_201 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_201 = FALSE; break; }
$_201 = TRUE; break;
}
while(0);
if( $_201 === TRUE ) { return $this->finalise($result); }
if( $_201 === FALSE) { return FALSE; }
}
function If_IfPart(&$res, $sub) {
$res['php'] =
'if (' . $sub['IfArgument']['php'] . ') { ' . PHP_EOL .
(isset($sub['Template']) ? $sub['Template']['php'] : '') . PHP_EOL .
'}';
}
function If_ElseIfPart(&$res, $sub) {
$res['php'] .=
'else if (' . $sub['IfArgument']['php'] . ') { ' . PHP_EOL .
$sub['Template']['php'] . PHP_EOL .
'}';
}
function If_ElsePart(&$res, $sub) {
$res['php'] .=
'else { ' . PHP_EOL .
$sub['Template']['php'] . PHP_EOL .
'}';
}
/* Require: '<%' < 'require' [ Call:(Method:Word "(" < :CallArguments > ")") > '%>' */
protected $match_Require_typestack = array('Require');
function match_Require ($stack = array()) {
$matchrule = "Require"; $result = $this->construct($matchrule, $matchrule, null);
$_217 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_217 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'require' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_217 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_217 = FALSE; break; }
$stack[] = $result; $result = $this->construct( $matchrule, "Call" );
$_213 = NULL;
do {
$matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Method" );
}
else { $_213 = FALSE; break; }
if (substr($this->string,$this->pos,1) == '(') {
$this->pos += 1;
$result["text"] .= '(';
}
else { $_213 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'CallArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "CallArguments" );
}
else { $_213 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ')') {
$this->pos += 1;
$result["text"] .= ')';
}
else { $_213 = FALSE; break; }
$_213 = TRUE; break;
}
while(0);
if( $_213 === TRUE ) {
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'Call' );
}
if( $_213 === FALSE) {
$result = array_pop($stack);
$_217 = FALSE; break;
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_217 = FALSE; break; }
$_217 = TRUE; break;
}
while(0);
if( $_217 === TRUE ) { return $this->finalise($result); }
if( $_217 === FALSE) { return FALSE; }
}
function Require_Call(&$res, $sub) {
$res['php'] = "Requirements::".$sub['Method']['text'].'('.$sub['CallArguments']['php'].');';
}
/* CacheBlockArgument:
!( "if " | "unless " )
(
:DollarMarkedLookup |
:QuotedString |
:Lookup
) */
protected $match_CacheBlockArgument_typestack = array('CacheBlockArgument');
function match_CacheBlockArgument ($stack = array()) {
$matchrule = "CacheBlockArgument"; $result = $this->construct($matchrule, $matchrule, null);
$_237 = NULL;
do {
$res_225 = $result;
$pos_225 = $this->pos;
$_224 = NULL;
do {
$_222 = NULL;
do {
$res_219 = $result;
$pos_219 = $this->pos;
if (( $subres = $this->literal( 'if ' ) ) !== FALSE) {
$result["text"] .= $subres;
$_222 = TRUE; break;
}
$result = $res_219;
$this->pos = $pos_219;
if (( $subres = $this->literal( 'unless ' ) ) !== FALSE) {
$result["text"] .= $subres;
$_222 = TRUE; break;
}
$result = $res_219;
$this->pos = $pos_219;
$_222 = FALSE; break;
}
while(0);
if( $_222 === FALSE) { $_224 = FALSE; break; }
$_224 = TRUE; break;
}
while(0);
if( $_224 === TRUE ) {
$result = $res_225;
$this->pos = $pos_225;
$_237 = FALSE; break;
}
if( $_224 === FALSE) {
$result = $res_225;
$this->pos = $pos_225;
}
$_235 = NULL;
do {
$_233 = NULL;
do {
$res_226 = $result;
$pos_226 = $this->pos;
$matcher = 'match_'.'DollarMarkedLookup'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "DollarMarkedLookup" );
$_233 = TRUE; break;
}
$result = $res_226;
$this->pos = $pos_226;
$_231 = NULL;
do {
$res_228 = $result;
$pos_228 = $this->pos;
$matcher = 'match_'.'QuotedString'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "QuotedString" );
$_231 = TRUE; break;
}
$result = $res_228;
$this->pos = $pos_228;
$matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Lookup" );
$_231 = TRUE; break;
}
$result = $res_228;
$this->pos = $pos_228;
$_231 = FALSE; break;
}
while(0);
if( $_231 === TRUE ) { $_233 = TRUE; break; }
$result = $res_226;
$this->pos = $pos_226;
$_233 = FALSE; break;
}
while(0);
if( $_233 === FALSE) { $_235 = FALSE; break; }
$_235 = TRUE; break;
}
while(0);
if( $_235 === FALSE) { $_237 = FALSE; break; }
$_237 = TRUE; break;
}
while(0);
if( $_237 === TRUE ) { return $this->finalise($result); }
if( $_237 === FALSE) { return FALSE; }
}
function CacheBlockArgument_DollarMarkedLookup(&$res, $sub) {
$res['php'] = $sub['Lookup']['php'];
}
function CacheBlockArgument_QuotedString(&$res, $sub) {
$res['php'] = "'" . str_replace("'", "\\'", $sub['String']['text']) . "'";
}
function CacheBlockArgument_Lookup(&$res, $sub) {
$res['php'] = $sub['php'];
}
/* CacheBlockArguments: CacheBlockArgument ( < "," < CacheBlockArgument )* */
protected $match_CacheBlockArguments_typestack = array('CacheBlockArguments');
function match_CacheBlockArguments ($stack = array()) {
$matchrule = "CacheBlockArguments"; $result = $this->construct($matchrule, $matchrule, null);
$_246 = NULL;
do {
$matcher = 'match_'.'CacheBlockArgument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_246 = FALSE; break; }
while (true) {
$res_245 = $result;
$pos_245 = $this->pos;
$_244 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ',') {
$this->pos += 1;
$result["text"] .= ',';
}
else { $_244 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'CacheBlockArgument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_244 = FALSE; break; }
$_244 = TRUE; break;
}
while(0);
if( $_244 === FALSE) {
$result = $res_245;
$this->pos = $pos_245;
unset( $res_245 );
unset( $pos_245 );
break;
}
}
$_246 = TRUE; break;
}
while(0);
if( $_246 === TRUE ) { return $this->finalise($result); }
if( $_246 === FALSE) { return FALSE; }
}
function CacheBlockArguments_CacheBlockArgument(&$res, $sub) {
if (!empty($res['php'])) $res['php'] .= ".'_'.";
else $res['php'] = '';
$res['php'] .= str_replace('$$FINAL', 'XML_val', $sub['php']);
}
/* CacheBlockTemplate: (Comment | If | Require | OldI18NTag | ClosedBlock | OpenBlock | MalformedBlock | Injection | Text)+ */
protected $match_CacheBlockTemplate_typestack = array('CacheBlockTemplate','Template');
function match_CacheBlockTemplate ($stack = array()) {
$matchrule = "CacheBlockTemplate"; $result = $this->construct($matchrule, $matchrule, array('TemplateMatcher' => 'CacheRestrictedTemplate'));
$count = 0;
while (true) {
$res_282 = $result;
$pos_282 = $this->pos;
$_281 = NULL;
do {
$_279 = NULL;
do {
$res_248 = $result;
$pos_248 = $this->pos;
$matcher = 'match_'.'Comment'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_279 = TRUE; break;
}
$result = $res_248;
$this->pos = $pos_248;
$_277 = NULL;
do {
$res_250 = $result;
$pos_250 = $this->pos;
$matcher = 'match_'.'If'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_277 = TRUE; break;
}
$result = $res_250;
$this->pos = $pos_250;
$_275 = NULL;
do {
$res_252 = $result;
$pos_252 = $this->pos;
$matcher = 'match_'.'Require'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_275 = TRUE; break;
}
$result = $res_252;
$this->pos = $pos_252;
$_273 = NULL;
do {
$res_254 = $result;
$pos_254 = $this->pos;
$matcher = 'match_'.'OldI18NTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_273 = TRUE; break;
}
$result = $res_254;
$this->pos = $pos_254;
$_271 = NULL;
do {
$res_256 = $result;
$pos_256 = $this->pos;
$matcher = 'match_'.'ClosedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_271 = TRUE; break;
}
$result = $res_256;
$this->pos = $pos_256;
$_269 = NULL;
do {
$res_258 = $result;
$pos_258 = $this->pos;
$matcher = 'match_'.'OpenBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_269 = TRUE; break;
}
$result = $res_258;
$this->pos = $pos_258;
$_267 = NULL;
do {
$res_260 = $result;
$pos_260 = $this->pos;
$matcher = 'match_'.'MalformedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_267 = TRUE; break;
}
$result = $res_260;
$this->pos = $pos_260;
$_265 = NULL;
do {
$res_262 = $result;
$pos_262 = $this->pos;
$matcher = 'match_'.'Injection'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_265 = TRUE; break;
}
$result = $res_262;
$this->pos = $pos_262;
$matcher = 'match_'.'Text'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_265 = TRUE; break;
}
$result = $res_262;
$this->pos = $pos_262;
$_265 = FALSE; break;
}
while(0);
if( $_265 === TRUE ) { $_267 = TRUE; break; }
$result = $res_260;
$this->pos = $pos_260;
$_267 = FALSE; break;
}
while(0);
if( $_267 === TRUE ) { $_269 = TRUE; break; }
$result = $res_258;
$this->pos = $pos_258;
$_269 = FALSE; break;
}
while(0);
if( $_269 === TRUE ) { $_271 = TRUE; break; }
$result = $res_256;
$this->pos = $pos_256;
$_271 = FALSE; break;
}
while(0);
if( $_271 === TRUE ) { $_273 = TRUE; break; }
$result = $res_254;
$this->pos = $pos_254;
$_273 = FALSE; break;
}
while(0);
if( $_273 === TRUE ) { $_275 = TRUE; break; }
$result = $res_252;
$this->pos = $pos_252;
$_275 = FALSE; break;
}
while(0);
if( $_275 === TRUE ) { $_277 = TRUE; break; }
$result = $res_250;
$this->pos = $pos_250;
$_277 = FALSE; break;
}
while(0);
if( $_277 === TRUE ) { $_279 = TRUE; break; }
$result = $res_248;
$this->pos = $pos_248;
$_279 = FALSE; break;
}
while(0);
if( $_279 === FALSE) { $_281 = FALSE; break; }
$_281 = TRUE; break;
}
while(0);
if( $_281 === FALSE) {
$result = $res_282;
$this->pos = $pos_282;
unset( $res_282 );
unset( $pos_282 );
break;
}
$count += 1;
}
if ($count > 0) { return $this->finalise($result); }
else { return FALSE; }
}
/* UncachedBlock:
'<%' < "uncached" < CacheBlockArguments? ( < Conditional:("if"|"unless") > Condition:IfArgument )? > '%>'
Template:$TemplateMatcher?
'<%' < 'end_' ("uncached"|"cached"|"cacheblock") > '%>' */
protected $match_UncachedBlock_typestack = array('UncachedBlock');
function match_UncachedBlock ($stack = array()) {
$matchrule = "UncachedBlock"; $result = $this->construct($matchrule, $matchrule, null);
$_319 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_319 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'uncached' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_319 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$res_287 = $result;
$pos_287 = $this->pos;
$matcher = 'match_'.'CacheBlockArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else {
$result = $res_287;
$this->pos = $pos_287;
unset( $res_287 );
unset( $pos_287 );
}
$res_299 = $result;
$pos_299 = $this->pos;
$_298 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$stack[] = $result; $result = $this->construct( $matchrule, "Conditional" );
$_294 = NULL;
do {
$_292 = NULL;
do {
$res_289 = $result;
$pos_289 = $this->pos;
if (( $subres = $this->literal( 'if' ) ) !== FALSE) {
$result["text"] .= $subres;
$_292 = TRUE; break;
}
$result = $res_289;
$this->pos = $pos_289;
if (( $subres = $this->literal( 'unless' ) ) !== FALSE) {
$result["text"] .= $subres;
$_292 = TRUE; break;
}
$result = $res_289;
$this->pos = $pos_289;
$_292 = FALSE; break;
}
while(0);
if( $_292 === FALSE) { $_294 = FALSE; break; }
$_294 = TRUE; break;
}
while(0);
if( $_294 === TRUE ) {
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'Conditional' );
}
if( $_294 === FALSE) {
$result = array_pop($stack);
$_298 = FALSE; break;
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'IfArgument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Condition" );
}
else { $_298 = FALSE; break; }
$_298 = TRUE; break;
}
while(0);
if( $_298 === FALSE) {
$result = $res_299;
$this->pos = $pos_299;
unset( $res_299 );
unset( $pos_299 );
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_319 = FALSE; break; }
$res_302 = $result;
$pos_302 = $this->pos;
$matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Template" );
}
else {
$result = $res_302;
$this->pos = $pos_302;
unset( $res_302 );
unset( $pos_302 );
}
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_319 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_319 = FALSE; break; }
$_315 = NULL;
do {
$_313 = NULL;
do {
$res_306 = $result;
$pos_306 = $this->pos;
if (( $subres = $this->literal( 'uncached' ) ) !== FALSE) {
$result["text"] .= $subres;
$_313 = TRUE; break;
}
$result = $res_306;
$this->pos = $pos_306;
$_311 = NULL;
do {
$res_308 = $result;
$pos_308 = $this->pos;
if (( $subres = $this->literal( 'cached' ) ) !== FALSE) {
$result["text"] .= $subres;
$_311 = TRUE; break;
}
$result = $res_308;
$this->pos = $pos_308;
if (( $subres = $this->literal( 'cacheblock' ) ) !== FALSE) {
$result["text"] .= $subres;
$_311 = TRUE; break;
}
$result = $res_308;
$this->pos = $pos_308;
$_311 = FALSE; break;
}
while(0);
if( $_311 === TRUE ) { $_313 = TRUE; break; }
$result = $res_306;
$this->pos = $pos_306;
$_313 = FALSE; break;
}
while(0);
if( $_313 === FALSE) { $_315 = FALSE; break; }
$_315 = TRUE; break;
}
while(0);
if( $_315 === FALSE) { $_319 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_319 = FALSE; break; }
$_319 = TRUE; break;
}
while(0);
if( $_319 === TRUE ) { return $this->finalise($result); }
if( $_319 === FALSE) { return FALSE; }
}
function UncachedBlock_Template(&$res, $sub){
$res['php'] = $sub['php'];
}
/* CacheRestrictedTemplate: (Comment | If | Require | CacheBlock | UncachedBlock | OldI18NTag | ClosedBlock | OpenBlock | MalformedBlock | Injection | Text)+ */
protected $match_CacheRestrictedTemplate_typestack = array('CacheRestrictedTemplate','Template');
function match_CacheRestrictedTemplate ($stack = array()) {
$matchrule = "CacheRestrictedTemplate"; $result = $this->construct($matchrule, $matchrule, null);
$count = 0;
while (true) {
$res_363 = $result;
$pos_363 = $this->pos;
$_362 = NULL;
do {
$_360 = NULL;
do {
$res_321 = $result;
$pos_321 = $this->pos;
$matcher = 'match_'.'Comment'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_360 = TRUE; break;
}
$result = $res_321;
$this->pos = $pos_321;
$_358 = NULL;
do {
$res_323 = $result;
$pos_323 = $this->pos;
$matcher = 'match_'.'If'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_358 = TRUE; break;
}
$result = $res_323;
$this->pos = $pos_323;
$_356 = NULL;
do {
$res_325 = $result;
$pos_325 = $this->pos;
$matcher = 'match_'.'Require'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_356 = TRUE; break;
}
$result = $res_325;
$this->pos = $pos_325;
$_354 = NULL;
do {
$res_327 = $result;
$pos_327 = $this->pos;
$matcher = 'match_'.'CacheBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_354 = TRUE; break;
}
$result = $res_327;
$this->pos = $pos_327;
$_352 = NULL;
do {
$res_329 = $result;
$pos_329 = $this->pos;
$matcher = 'match_'.'UncachedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_352 = TRUE; break;
}
$result = $res_329;
$this->pos = $pos_329;
$_350 = NULL;
do {
$res_331 = $result;
$pos_331 = $this->pos;
$matcher = 'match_'.'OldI18NTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_350 = TRUE; break;
}
$result = $res_331;
$this->pos = $pos_331;
$_348 = NULL;
do {
$res_333 = $result;
$pos_333 = $this->pos;
$matcher = 'match_'.'ClosedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_348 = TRUE; break;
}
$result = $res_333;
$this->pos = $pos_333;
$_346 = NULL;
do {
$res_335 = $result;
$pos_335 = $this->pos;
$matcher = 'match_'.'OpenBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_346 = TRUE; break;
}
$result = $res_335;
$this->pos = $pos_335;
$_344 = NULL;
do {
$res_337 = $result;
$pos_337 = $this->pos;
$matcher = 'match_'.'MalformedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_344 = TRUE; break;
}
$result = $res_337;
$this->pos = $pos_337;
$_342 = NULL;
do {
$res_339 = $result;
$pos_339 = $this->pos;
$matcher = 'match_'.'Injection'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_342 = TRUE; break;
}
$result = $res_339;
$this->pos = $pos_339;
$matcher = 'match_'.'Text'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_342 = TRUE; break;
}
$result = $res_339;
$this->pos = $pos_339;
$_342 = FALSE; break;
}
while(0);
if( $_342 === TRUE ) { $_344 = TRUE; break; }
$result = $res_337;
$this->pos = $pos_337;
$_344 = FALSE; break;
}
while(0);
if( $_344 === TRUE ) { $_346 = TRUE; break; }
$result = $res_335;
$this->pos = $pos_335;
$_346 = FALSE; break;
}
while(0);
if( $_346 === TRUE ) { $_348 = TRUE; break; }
$result = $res_333;
$this->pos = $pos_333;
$_348 = FALSE; break;
}
while(0);
if( $_348 === TRUE ) { $_350 = TRUE; break; }
$result = $res_331;
$this->pos = $pos_331;
$_350 = FALSE; break;
}
while(0);
if( $_350 === TRUE ) { $_352 = TRUE; break; }
$result = $res_329;
$this->pos = $pos_329;
$_352 = FALSE; break;
}
while(0);
if( $_352 === TRUE ) { $_354 = TRUE; break; }
$result = $res_327;
$this->pos = $pos_327;
$_354 = FALSE; break;
}
while(0);
if( $_354 === TRUE ) { $_356 = TRUE; break; }
$result = $res_325;
$this->pos = $pos_325;
$_356 = FALSE; break;
}
while(0);
if( $_356 === TRUE ) { $_358 = TRUE; break; }
$result = $res_323;
$this->pos = $pos_323;
$_358 = FALSE; break;
}
while(0);
if( $_358 === TRUE ) { $_360 = TRUE; break; }
$result = $res_321;
$this->pos = $pos_321;
$_360 = FALSE; break;
}
while(0);
if( $_360 === FALSE) { $_362 = FALSE; break; }
$_362 = TRUE; break;
}
while(0);
if( $_362 === FALSE) {
$result = $res_363;
$this->pos = $pos_363;
unset( $res_363 );
unset( $pos_363 );
break;
}
$count += 1;
}
if ($count > 0) { return $this->finalise($result); }
else { return FALSE; }
}
function CacheRestrictedTemplate_CacheBlock(&$res, $sub) {
throw new SSTemplateParseException('You cant have cache blocks nested within with, loop or control blocks that are within cache blocks', $this);
}
function CacheRestrictedTemplate_UncachedBlock(&$res, $sub) {
throw new SSTemplateParseException('You cant have uncache blocks nested within with, loop or control blocks that are within cache blocks', $this);
}
/* CacheBlock:
'<%' < CacheTag:("cached"|"cacheblock") < (CacheBlockArguments)? ( < Conditional:("if"|"unless") > Condition:IfArgument )? > '%>'
(CacheBlock | UncachedBlock | CacheBlockTemplate)*
'<%' < 'end_' ("cached"|"uncached"|"cacheblock") > '%>' */
protected $match_CacheBlock_typestack = array('CacheBlock');
function match_CacheBlock ($stack = array()) {
$matchrule = "CacheBlock"; $result = $this->construct($matchrule, $matchrule, null);
$_418 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_418 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$stack[] = $result; $result = $this->construct( $matchrule, "CacheTag" );
$_371 = NULL;
do {
$_369 = NULL;
do {
$res_366 = $result;
$pos_366 = $this->pos;
if (( $subres = $this->literal( 'cached' ) ) !== FALSE) {
$result["text"] .= $subres;
$_369 = TRUE; break;
}
$result = $res_366;
$this->pos = $pos_366;
if (( $subres = $this->literal( 'cacheblock' ) ) !== FALSE) {
$result["text"] .= $subres;
$_369 = TRUE; break;
}
$result = $res_366;
$this->pos = $pos_366;
$_369 = FALSE; break;
}
while(0);
if( $_369 === FALSE) { $_371 = FALSE; break; }
$_371 = TRUE; break;
}
while(0);
if( $_371 === TRUE ) {
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'CacheTag' );
}
if( $_371 === FALSE) {
$result = array_pop($stack);
$_418 = FALSE; break;
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$res_376 = $result;
$pos_376 = $this->pos;
$_375 = NULL;
do {
$matcher = 'match_'.'CacheBlockArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_375 = FALSE; break; }
$_375 = TRUE; break;
}
while(0);
if( $_375 === FALSE) {
$result = $res_376;
$this->pos = $pos_376;
unset( $res_376 );
unset( $pos_376 );
}
$res_388 = $result;
$pos_388 = $this->pos;
$_387 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$stack[] = $result; $result = $this->construct( $matchrule, "Conditional" );
$_383 = NULL;
do {
$_381 = NULL;
do {
$res_378 = $result;
$pos_378 = $this->pos;
if (( $subres = $this->literal( 'if' ) ) !== FALSE) {
$result["text"] .= $subres;
$_381 = TRUE; break;
}
$result = $res_378;
$this->pos = $pos_378;
if (( $subres = $this->literal( 'unless' ) ) !== FALSE) {
$result["text"] .= $subres;
$_381 = TRUE; break;
}
$result = $res_378;
$this->pos = $pos_378;
$_381 = FALSE; break;
}
while(0);
if( $_381 === FALSE) { $_383 = FALSE; break; }
$_383 = TRUE; break;
}
while(0);
if( $_383 === TRUE ) {
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'Conditional' );
}
if( $_383 === FALSE) {
$result = array_pop($stack);
$_387 = FALSE; break;
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'IfArgument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Condition" );
}
else { $_387 = FALSE; break; }
$_387 = TRUE; break;
}
while(0);
if( $_387 === FALSE) {
$result = $res_388;
$this->pos = $pos_388;
unset( $res_388 );
unset( $pos_388 );
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_418 = FALSE; break; }
while (true) {
$res_401 = $result;
$pos_401 = $this->pos;
$_400 = NULL;
do {
$_398 = NULL;
do {
$res_391 = $result;
$pos_391 = $this->pos;
$matcher = 'match_'.'CacheBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_398 = TRUE; break;
}
$result = $res_391;
$this->pos = $pos_391;
$_396 = NULL;
do {
$res_393 = $result;
$pos_393 = $this->pos;
$matcher = 'match_'.'UncachedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_396 = TRUE; break;
}
$result = $res_393;
$this->pos = $pos_393;
$matcher = 'match_'.'CacheBlockTemplate'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_396 = TRUE; break;
}
$result = $res_393;
$this->pos = $pos_393;
$_396 = FALSE; break;
}
while(0);
if( $_396 === TRUE ) { $_398 = TRUE; break; }
$result = $res_391;
$this->pos = $pos_391;
$_398 = FALSE; break;
}
while(0);
if( $_398 === FALSE) { $_400 = FALSE; break; }
$_400 = TRUE; break;
}
while(0);
if( $_400 === FALSE) {
$result = $res_401;
$this->pos = $pos_401;
unset( $res_401 );
unset( $pos_401 );
break;
}
}
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_418 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_418 = FALSE; break; }
$_414 = NULL;
do {
$_412 = NULL;
do {
$res_405 = $result;
$pos_405 = $this->pos;
if (( $subres = $this->literal( 'cached' ) ) !== FALSE) {
$result["text"] .= $subres;
$_412 = TRUE; break;
}
$result = $res_405;
$this->pos = $pos_405;
$_410 = NULL;
do {
$res_407 = $result;
$pos_407 = $this->pos;
if (( $subres = $this->literal( 'uncached' ) ) !== FALSE) {
$result["text"] .= $subres;
$_410 = TRUE; break;
}
$result = $res_407;
$this->pos = $pos_407;
if (( $subres = $this->literal( 'cacheblock' ) ) !== FALSE) {
$result["text"] .= $subres;
$_410 = TRUE; break;
}
$result = $res_407;
$this->pos = $pos_407;
$_410 = FALSE; break;
}
while(0);
if( $_410 === TRUE ) { $_412 = TRUE; break; }
$result = $res_405;
$this->pos = $pos_405;
$_412 = FALSE; break;
}
while(0);
if( $_412 === FALSE) { $_414 = FALSE; break; }
$_414 = TRUE; break;
}
while(0);
if( $_414 === FALSE) { $_418 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_418 = FALSE; break; }
$_418 = TRUE; break;
}
while(0);
if( $_418 === TRUE ) { return $this->finalise($result); }
if( $_418 === FALSE) { return FALSE; }
}
function CacheBlock__construct(&$res){
$res['subblocks'] = 0;
}
function CacheBlock_CacheBlockArguments(&$res, $sub){
$res['key'] = !empty($sub['php']) ? $sub['php'] : '';
}
function CacheBlock_Condition(&$res, $sub){
$res['condition'] = ($res['Conditional']['text'] == 'if' ? '(' : '!(') . $sub['php'] . ') && ';
}
function CacheBlock_CacheBlock(&$res, $sub){
$res['php'] .= $sub['php'];
}
function CacheBlock_UncachedBlock(&$res, $sub){
$res['php'] .= $sub['php'];
}
function CacheBlock_CacheBlockTemplate(&$res, $sub){
// Get the block counter
$block = ++$res['subblocks'];
// Build the key for this block from the passed cache key, the block index, and the sha hash of the template itself
$key = "'" . sha1($sub['php']) . (isset($res['key']) && $res['key'] ? "_'.sha1(".$res['key'].")" : "'") . ".'_$block'";
// Get any condition
$condition = isset($res['condition']) ? $res['condition'] : '';
$res['php'] .= 'if ('.$condition.'($partial = $cache->load('.$key.'))) $val .= $partial;' . PHP_EOL;
$res['php'] .= 'else { $oldval = $val; $val = "";' . PHP_EOL;
$res['php'] .= $sub['php'] . PHP_EOL;
$res['php'] .= $condition . ' $cache->save($val); $val = $oldval . $val;' . PHP_EOL;
$res['php'] .= '}';
}
/* OldTPart: "_t" < "(" < QuotedString (< "," < CallArguments)? > ")" */
protected $match_OldTPart_typestack = array('OldTPart');
function match_OldTPart ($stack = array()) {
$matchrule = "OldTPart"; $result = $this->construct($matchrule, $matchrule, null);
$_433 = NULL;
do {
if (( $subres = $this->literal( '_t' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_433 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == '(') {
$this->pos += 1;
$result["text"] .= '(';
}
else { $_433 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'QuotedString'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_433 = FALSE; break; }
$res_430 = $result;
$pos_430 = $this->pos;
$_429 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ',') {
$this->pos += 1;
$result["text"] .= ',';
}
else { $_429 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'CallArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_429 = FALSE; break; }
$_429 = TRUE; break;
}
while(0);
if( $_429 === FALSE) {
$result = $res_430;
$this->pos = $pos_430;
unset( $res_430 );
unset( $pos_430 );
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ')') {
$this->pos += 1;
$result["text"] .= ')';
}
else { $_433 = FALSE; break; }
$_433 = TRUE; break;
}
while(0);
if( $_433 === TRUE ) { return $this->finalise($result); }
if( $_433 === FALSE) { return FALSE; }
}
function OldTPart__construct(&$res) {
$res['php'] = "_t(";
}
function OldTPart_QuotedString(&$res, $sub) {
$entity = $sub['String']['text'];
if (strpos($entity, '.') === false) {
$res['php'] .= "\$scope->XML_val('I18NNamespace').'.$entity'";
}
else {
$res['php'] .= "'$entity'";
}
}
function OldTPart_CallArguments(&$res, $sub) {
$res['php'] .= ',' . $sub['php'];
}
function OldTPart__finalise(&$res) {
$res['php'] .= ')';
}
/* OldTTag: "<%" < OldTPart > "%>" */
protected $match_OldTTag_typestack = array('OldTTag');
function match_OldTTag ($stack = array()) {
$matchrule = "OldTTag"; $result = $this->construct($matchrule, $matchrule, null);
$_440 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_440 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'OldTPart'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_440 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_440 = FALSE; break; }
$_440 = TRUE; break;
}
while(0);
if( $_440 === TRUE ) { return $this->finalise($result); }
if( $_440 === FALSE) { return FALSE; }
}
function OldTTag_OldTPart(&$res, $sub) {
$res['php'] = $sub['php'];
}
/* OldSprintfTag: "<%" < "sprintf" < "(" < OldTPart < "," < CallArguments > ")" > "%>" */
protected $match_OldSprintfTag_typestack = array('OldSprintfTag');
function match_OldSprintfTag ($stack = array()) {
$matchrule = "OldSprintfTag"; $result = $this->construct($matchrule, $matchrule, null);
$_457 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_457 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'sprintf' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_457 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == '(') {
$this->pos += 1;
$result["text"] .= '(';
}
else { $_457 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'OldTPart'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_457 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ',') {
$this->pos += 1;
$result["text"] .= ',';
}
else { $_457 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'CallArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) { $this->store( $result, $subres ); }
else { $_457 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ')') {
$this->pos += 1;
$result["text"] .= ')';
}
else { $_457 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_457 = FALSE; break; }
$_457 = TRUE; break;
}
while(0);
if( $_457 === TRUE ) { return $this->finalise($result); }
if( $_457 === FALSE) { return FALSE; }
}
function OldSprintfTag__construct(&$res) {
$res['php'] = "sprintf(";
}
function OldSprintfTag_OldTPart(&$res, $sub) {
$res['php'] .= $sub['php'];
}
function OldSprintfTag_CallArguments(&$res, $sub) {
$res['php'] .= ',' . $sub['php'] . ')';
}
/* OldI18NTag: OldSprintfTag | OldTTag */
protected $match_OldI18NTag_typestack = array('OldI18NTag');
function match_OldI18NTag ($stack = array()) {
$matchrule = "OldI18NTag"; $result = $this->construct($matchrule, $matchrule, null);
$_462 = NULL;
do {
$res_459 = $result;
$pos_459 = $this->pos;
$matcher = 'match_'.'OldSprintfTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_462 = TRUE; break;
}
$result = $res_459;
$this->pos = $pos_459;
$matcher = 'match_'.'OldTTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_462 = TRUE; break;
}
$result = $res_459;
$this->pos = $pos_459;
$_462 = FALSE; break;
}
while(0);
if( $_462 === TRUE ) { return $this->finalise($result); }
if( $_462 === FALSE) { return FALSE; }
}
function OldI18NTag_STR(&$res, $sub) {
$res['php'] = '$val .= ' . $sub['php'] . ';';
}
/* BlockArguments: :Argument ( < "," < :Argument)* */
protected $match_BlockArguments_typestack = array('BlockArguments');
function match_BlockArguments ($stack = array()) {
$matchrule = "BlockArguments"; $result = $this->construct($matchrule, $matchrule, null);
$_471 = NULL;
do {
$matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Argument" );
}
else { $_471 = FALSE; break; }
while (true) {
$res_470 = $result;
$pos_470 = $this->pos;
$_469 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (substr($this->string,$this->pos,1) == ',') {
$this->pos += 1;
$result["text"] .= ',';
}
else { $_469 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Argument" );
}
else { $_469 = FALSE; break; }
$_469 = TRUE; break;
}
while(0);
if( $_469 === FALSE) {
$result = $res_470;
$this->pos = $pos_470;
unset( $res_470 );
unset( $pos_470 );
break;
}
}
$_471 = TRUE; break;
}
while(0);
if( $_471 === TRUE ) { return $this->finalise($result); }
if( $_471 === FALSE) { return FALSE; }
}
/* NotBlockTag: "end_" | (("if" | "else_if" | "else" | "require" | "cached" | "uncached" | "cacheblock") ] ) */
protected $match_NotBlockTag_typestack = array('NotBlockTag');
function match_NotBlockTag ($stack = array()) {
$matchrule = "NotBlockTag"; $result = $this->construct($matchrule, $matchrule, null);
$_505 = NULL;
do {
$res_473 = $result;
$pos_473 = $this->pos;
if (( $subres = $this->literal( 'end_' ) ) !== FALSE) {
$result["text"] .= $subres;
$_505 = TRUE; break;
}
$result = $res_473;
$this->pos = $pos_473;
$_503 = NULL;
do {
$_500 = NULL;
do {
$_498 = NULL;
do {
$res_475 = $result;
$pos_475 = $this->pos;
if (( $subres = $this->literal( 'if' ) ) !== FALSE) {
$result["text"] .= $subres;
$_498 = TRUE; break;
}
$result = $res_475;
$this->pos = $pos_475;
$_496 = NULL;
do {
$res_477 = $result;
$pos_477 = $this->pos;
if (( $subres = $this->literal( 'else_if' ) ) !== FALSE) {
$result["text"] .= $subres;
$_496 = TRUE; break;
}
$result = $res_477;
$this->pos = $pos_477;
$_494 = NULL;
do {
$res_479 = $result;
$pos_479 = $this->pos;
if (( $subres = $this->literal( 'else' ) ) !== FALSE) {
$result["text"] .= $subres;
$_494 = TRUE; break;
}
$result = $res_479;
$this->pos = $pos_479;
$_492 = NULL;
do {
$res_481 = $result;
$pos_481 = $this->pos;
if (( $subres = $this->literal( 'require' ) ) !== FALSE) {
$result["text"] .= $subres;
$_492 = TRUE; break;
}
$result = $res_481;
$this->pos = $pos_481;
$_490 = NULL;
do {
$res_483 = $result;
$pos_483 = $this->pos;
if (( $subres = $this->literal( 'cached' ) ) !== FALSE) {
$result["text"] .= $subres;
$_490 = TRUE; break;
}
$result = $res_483;
$this->pos = $pos_483;
$_488 = NULL;
do {
$res_485 = $result;
$pos_485 = $this->pos;
if (( $subres = $this->literal( 'uncached' ) ) !== FALSE) {
$result["text"] .= $subres;
$_488 = TRUE; break;
}
$result = $res_485;
$this->pos = $pos_485;
if (( $subres = $this->literal( 'cacheblock' ) ) !== FALSE) {
$result["text"] .= $subres;
$_488 = TRUE; break;
}
$result = $res_485;
$this->pos = $pos_485;
$_488 = FALSE; break;
}
while(0);
if( $_488 === TRUE ) { $_490 = TRUE; break; }
$result = $res_483;
$this->pos = $pos_483;
$_490 = FALSE; break;
}
while(0);
if( $_490 === TRUE ) { $_492 = TRUE; break; }
$result = $res_481;
$this->pos = $pos_481;
$_492 = FALSE; break;
}
while(0);
if( $_492 === TRUE ) { $_494 = TRUE; break; }
$result = $res_479;
$this->pos = $pos_479;
$_494 = FALSE; break;
}
while(0);
if( $_494 === TRUE ) { $_496 = TRUE; break; }
$result = $res_477;
$this->pos = $pos_477;
$_496 = FALSE; break;
}
while(0);
if( $_496 === TRUE ) { $_498 = TRUE; break; }
$result = $res_475;
$this->pos = $pos_475;
$_498 = FALSE; break;
}
while(0);
if( $_498 === FALSE) { $_500 = FALSE; break; }
$_500 = TRUE; break;
}
while(0);
if( $_500 === FALSE) { $_503 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_503 = FALSE; break; }
$_503 = TRUE; break;
}
while(0);
if( $_503 === TRUE ) { $_505 = TRUE; break; }
$result = $res_473;
$this->pos = $pos_473;
$_505 = FALSE; break;
}
while(0);
if( $_505 === TRUE ) { return $this->finalise($result); }
if( $_505 === FALSE) { return FALSE; }
}
/* ClosedBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > Zap:'%>' Template:$TemplateMatcher? '<%' < 'end_' '$BlockName' > '%>' */
protected $match_ClosedBlock_typestack = array('ClosedBlock');
function match_ClosedBlock ($stack = array()) {
$matchrule = "ClosedBlock"; $result = $this->construct($matchrule, $matchrule, null);
$_525 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_525 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$res_509 = $result;
$pos_509 = $this->pos;
$matcher = 'match_'.'NotBlockTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$result = $res_509;
$this->pos = $pos_509;
$_525 = FALSE; break;
}
else {
$result = $res_509;
$this->pos = $pos_509;
}
$matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "BlockName" );
}
else { $_525 = FALSE; break; }
$res_515 = $result;
$pos_515 = $this->pos;
$_514 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_514 = FALSE; break; }
$matcher = 'match_'.'BlockArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "BlockArguments" );
}
else { $_514 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_514 = FALSE; break; }
$_514 = TRUE; break;
}
while(0);
if( $_514 === FALSE) {
$result = $res_515;
$this->pos = $pos_515;
unset( $res_515 );
unset( $pos_515 );
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$stack[] = $result; $result = $this->construct( $matchrule, "Zap" );
if (( $subres = $this->literal( '%>' ) ) !== FALSE) {
$result["text"] .= $subres;
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'Zap' );
}
else {
$result = array_pop($stack);
$_525 = FALSE; break;
}
$res_518 = $result;
$pos_518 = $this->pos;
$matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Template" );
}
else {
$result = $res_518;
$this->pos = $pos_518;
unset( $res_518 );
unset( $pos_518 );
}
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_525 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_525 = FALSE; break; }
if (( $subres = $this->literal( ''.$this->expression($result, $stack, 'BlockName').'' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_525 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_525 = FALSE; break; }
$_525 = TRUE; break;
}
while(0);
if( $_525 === TRUE ) { return $this->finalise($result); }
if( $_525 === FALSE) { return FALSE; }
}
/**
* As mentioned in the parser comment, block handling is kept fairly generic for extensibility. The match rule
* builds up two important elements in the match result array:
* 'ArgumentCount' - how many arguments were passed in the opening tag
* 'Arguments' an array of the Argument match rule result arrays
*
* Once a block has successfully been matched against, it will then look for the actual handler, which should
* be on this class (either defined or decorated on) as ClosedBlock_Handler_Name(&$res), where Name is the
* tag name, first letter captialized (i.e Control, Loop, With, etc).
*
* This function will be called with the match rule result array as it's first argument. It should return
* the php result of this block as it's return value, or throw an error if incorrect arguments were passed.
*/
function ClosedBlock__construct(&$res) {
$res['ArgumentCount'] = 0;
}
function ClosedBlock_BlockArguments(&$res, $sub) {
if (isset($sub['Argument']['ArgumentMode'])) {
$res['Arguments'] = array($sub['Argument']);
$res['ArgumentCount'] = 1;
}
else {
$res['Arguments'] = $sub['Argument'];
$res['ArgumentCount'] = count($res['Arguments']);
}
}
function ClosedBlock__finalise(&$res) {
$blockname = $res['BlockName']['text'];
$method = 'ClosedBlock_Handle_'.ucfirst(strtolower($blockname));
if (method_exists($this, $method)) $res['php'] = $this->$method($res);
else {
throw new SSTemplateParseException('Unknown closed block "'.$blockname.'" encountered. Perhaps you are not supposed to close this block, or have mis-spelled it?', $this);
}
}
/**
* This is an example of a block handler function. This one handles the loop tag.
*/
function ClosedBlock_Handle_Loop(&$res) {
if ($res['ArgumentCount'] != 1) {
throw new SSTemplateParseException('Either no or too many arguments in control block. Must be one argument only.', $this);
}
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') {
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
}
$on = str_replace('$$FINAL', 'obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
return
$on . '; $scope->pushScope(); while (($key = $scope->next()) !== false) {' . PHP_EOL .
$res['Template']['php'] . PHP_EOL .
'}; $scope->popScope(); ';
}
/**
* The deprecated closed block handler for control blocks
* @deprecated
*/
function ClosedBlock_Handle_Control(&$res) {
return $this->ClosedBlock_Handle_Loop($res);
}
/**
* The closed block handler for with blocks
*/
function ClosedBlock_Handle_With(&$res) {
if ($res['ArgumentCount'] != 1) {
throw new SSTemplateParseException('Either no or too many arguments in with block. Must be one argument only.', $this);
}
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') {
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
}
$on = str_replace('$$FINAL', 'obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
return
$on . '; $scope->pushScope();' . PHP_EOL .
$res['Template']['php'] . PHP_EOL .
'; $scope->popScope(); ';
}
/* OpenBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > '%>' */
protected $match_OpenBlock_typestack = array('OpenBlock');
function match_OpenBlock ($stack = array()) {
$matchrule = "OpenBlock"; $result = $this->construct($matchrule, $matchrule, null);
$_538 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_538 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$res_529 = $result;
$pos_529 = $this->pos;
$matcher = 'match_'.'NotBlockTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$result = $res_529;
$this->pos = $pos_529;
$_538 = FALSE; break;
}
else {
$result = $res_529;
$this->pos = $pos_529;
}
$matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "BlockName" );
}
else { $_538 = FALSE; break; }
$res_535 = $result;
$pos_535 = $this->pos;
$_534 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_534 = FALSE; break; }
$matcher = 'match_'.'BlockArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "BlockArguments" );
}
else { $_534 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_534 = FALSE; break; }
$_534 = TRUE; break;
}
while(0);
if( $_534 === FALSE) {
$result = $res_535;
$this->pos = $pos_535;
unset( $res_535 );
unset( $pos_535 );
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_538 = FALSE; break; }
$_538 = TRUE; break;
}
while(0);
if( $_538 === TRUE ) { return $this->finalise($result); }
if( $_538 === FALSE) { return FALSE; }
}
function OpenBlock__construct(&$res) {
$res['ArgumentCount'] = 0;
}
function OpenBlock_BlockArguments(&$res, $sub) {
if (isset($sub['Argument']['ArgumentMode'])) {
$res['Arguments'] = array($sub['Argument']);
$res['ArgumentCount'] = 1;
}
else {
$res['Arguments'] = $sub['Argument'];
$res['ArgumentCount'] = count($res['Arguments']);
}
}
function OpenBlock__finalise(&$res) {
$blockname = $res['BlockName']['text'];
$method = 'OpenBlock_Handle_'.ucfirst(strtolower($blockname));
if (method_exists($this, $method)) $res['php'] = $this->$method($res);
else {
throw new SSTemplateParseException('Unknown open block "'.$blockname.'" encountered. Perhaps you missed the closing tag or have mis-spelled it?', $this);
}
}
/**
* This is an open block handler, for the <% include %> tag
*/
function OpenBlock_Handle_Include(&$res) {
if ($res['ArgumentCount'] != 1) throw new SSTemplateParseException('Include takes exactly one argument', $this);
$arg = $res['Arguments'][0];
$php = ($arg['ArgumentMode'] == 'default') ? $arg['string_php'] : $arg['php'];
if($this->includeDebuggingComments) { // Add include filename comments on dev sites
return
'$val .= \'<!-- include '.$php.' -->\';'. "\n".
'$val .= SSViewer::parse_template('.$php.', $scope->getItem());'. "\n".
'$val .= \'<!-- end include '.$php.' -->\';'. "\n";
}
else {
return
'$val .= SSViewer::execute_template('.$php.', $scope->getItem());'. "\n";
}
}
/**
* This is an open block handler, for the <% debug %> utility tag
*/
function OpenBlock_Handle_Debug(&$res) {
if ($res['ArgumentCount'] == 0) return '$scope->debug();';
else if ($res['ArgumentCount'] == 1) {
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') return 'Debug::show('.$arg['php'].');';
$php = ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php'];
return '$val .= Debug::show('.str_replace('FINALGET!', 'cachedCall', $php).');';
}
else {
throw new SSTemplateParseException('Debug takes 0 or 1 argument only.', $this);
}
}
/**
* This is an open block handler, for the <% base_tag %> tag
*/
function OpenBlock_Handle_Base_tag(&$res) {
if ($res['ArgumentCount'] != 0) throw new SSTemplateParseException('Base_tag takes no arguments', $this);
return '$val .= SSViewer::get_base_tag($val);';
}
/**
* This is an open block handler, for the <% current_page %> tag
*/
function OpenBlock_Handle_Current_page(&$res) {
if ($res['ArgumentCount'] != 0) throw new SSTemplateParseException('Current_page takes no arguments', $this);
return '$val .= $_SERVER[SCRIPT_URL];';
}
/* MismatchedEndBlock: '<%' < 'end_' :Word > '%>' */
protected $match_MismatchedEndBlock_typestack = array('MismatchedEndBlock');
function match_MismatchedEndBlock ($stack = array()) {
$matchrule = "MismatchedEndBlock"; $result = $this->construct($matchrule, $matchrule, null);
$_546 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_546 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_546 = FALSE; break; }
$matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Word" );
}
else { $_546 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_546 = FALSE; break; }
$_546 = TRUE; break;
}
while(0);
if( $_546 === TRUE ) { return $this->finalise($result); }
if( $_546 === FALSE) { return FALSE; }
}
function MismatchedEndBlock__finalise(&$res) {
$blockname = $res['Word']['text'];
throw new SSTemplateParseException('Unexpected close tag end_'.$blockname.' encountered. Perhaps you have mis-nested blocks, or have mis-spelled a tag?', $this);
}
/* MalformedOpenTag: '<%' < !NotBlockTag Tag:Word !( ( [ :BlockArguments ] )? > '%>' ) */
protected $match_MalformedOpenTag_typestack = array('MalformedOpenTag');
function match_MalformedOpenTag ($stack = array()) {
$matchrule = "MalformedOpenTag"; $result = $this->construct($matchrule, $matchrule, null);
$_561 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_561 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$res_550 = $result;
$pos_550 = $this->pos;
$matcher = 'match_'.'NotBlockTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$result = $res_550;
$this->pos = $pos_550;
$_561 = FALSE; break;
}
else {
$result = $res_550;
$this->pos = $pos_550;
}
$matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Tag" );
}
else { $_561 = FALSE; break; }
$res_560 = $result;
$pos_560 = $this->pos;
$_559 = NULL;
do {
$res_556 = $result;
$pos_556 = $this->pos;
$_555 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_555 = FALSE; break; }
$matcher = 'match_'.'BlockArguments'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "BlockArguments" );
}
else { $_555 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_555 = FALSE; break; }
$_555 = TRUE; break;
}
while(0);
if( $_555 === FALSE) {
$result = $res_556;
$this->pos = $pos_556;
unset( $res_556 );
unset( $pos_556 );
}
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_559 = FALSE; break; }
$_559 = TRUE; break;
}
while(0);
if( $_559 === TRUE ) {
$result = $res_560;
$this->pos = $pos_560;
$_561 = FALSE; break;
}
if( $_559 === FALSE) {
$result = $res_560;
$this->pos = $pos_560;
}
$_561 = TRUE; break;
}
while(0);
if( $_561 === TRUE ) { return $this->finalise($result); }
if( $_561 === FALSE) { return FALSE; }
}
function MalformedOpenTag__finalise(&$res) {
$tag = $res['Tag']['text'];
throw new SSTemplateParseException("Malformed opening block tag $tag. Perhaps you have tried to use operators?", $this);
}
/* MalformedCloseTag: '<%' < Tag:('end_' :Word ) !( > '%>' ) */
protected $match_MalformedCloseTag_typestack = array('MalformedCloseTag');
function match_MalformedCloseTag ($stack = array()) {
$matchrule = "MalformedCloseTag"; $result = $this->construct($matchrule, $matchrule, null);
$_573 = NULL;
do {
if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_573 = FALSE; break; }
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
$stack[] = $result; $result = $this->construct( $matchrule, "Tag" );
$_567 = NULL;
do {
if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_567 = FALSE; break; }
$matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres, "Word" );
}
else { $_567 = FALSE; break; }
$_567 = TRUE; break;
}
while(0);
if( $_567 === TRUE ) {
$subres = $result; $result = array_pop($stack);
$this->store( $result, $subres, 'Tag' );
}
if( $_567 === FALSE) {
$result = array_pop($stack);
$_573 = FALSE; break;
}
$res_572 = $result;
$pos_572 = $this->pos;
$_571 = NULL;
do {
if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; }
if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_571 = FALSE; break; }
$_571 = TRUE; break;
}
while(0);
if( $_571 === TRUE ) {
$result = $res_572;
$this->pos = $pos_572;
$_573 = FALSE; break;
}
if( $_571 === FALSE) {
$result = $res_572;
$this->pos = $pos_572;
}
$_573 = TRUE; break;
}
while(0);
if( $_573 === TRUE ) { return $this->finalise($result); }
if( $_573 === FALSE) { return FALSE; }
}
function MalformedCloseTag__finalise(&$res) {
$tag = $res['Tag']['text'];
throw new SSTemplateParseException("Malformed closing block tag $tag. Perhaps you have tried to pass an argument to one?", $this);
}
/* MalformedBlock: MalformedOpenTag | MalformedCloseTag */
protected $match_MalformedBlock_typestack = array('MalformedBlock');
function match_MalformedBlock ($stack = array()) {
$matchrule = "MalformedBlock"; $result = $this->construct($matchrule, $matchrule, null);
$_578 = NULL;
do {
$res_575 = $result;
$pos_575 = $this->pos;
$matcher = 'match_'.'MalformedOpenTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_578 = TRUE; break;
}
$result = $res_575;
$this->pos = $pos_575;
$matcher = 'match_'.'MalformedCloseTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_578 = TRUE; break;
}
$result = $res_575;
$this->pos = $pos_575;
$_578 = FALSE; break;
}
while(0);
if( $_578 === TRUE ) { return $this->finalise($result); }
if( $_578 === FALSE) { return FALSE; }
}
/* Comment: "<%--" (!"--%>" /./)+ "--%>" */
protected $match_Comment_typestack = array('Comment');
function match_Comment ($stack = array()) {
$matchrule = "Comment"; $result = $this->construct($matchrule, $matchrule, null);
$_586 = NULL;
do {
if (( $subres = $this->literal( '<%--' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_586 = FALSE; break; }
$count = 0;
while (true) {
$res_584 = $result;
$pos_584 = $this->pos;
$_583 = NULL;
do {
$res_581 = $result;
$pos_581 = $this->pos;
if (( $subres = $this->literal( '--%>' ) ) !== FALSE) {
$result["text"] .= $subres;
$result = $res_581;
$this->pos = $pos_581;
$_583 = FALSE; break;
}
else {
$result = $res_581;
$this->pos = $pos_581;
}
if (( $subres = $this->rx( '/./' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_583 = FALSE; break; }
$_583 = TRUE; break;
}
while(0);
if( $_583 === FALSE) {
$result = $res_584;
$this->pos = $pos_584;
unset( $res_584 );
unset( $pos_584 );
break;
}
$count += 1;
}
if ($count > 0) { }
else { $_586 = FALSE; break; }
if (( $subres = $this->literal( '--%>' ) ) !== FALSE) { $result["text"] .= $subres; }
else { $_586 = FALSE; break; }
$_586 = TRUE; break;
}
while(0);
if( $_586 === TRUE ) { return $this->finalise($result); }
if( $_586 === FALSE) { return FALSE; }
}
function Comment__construct(&$res) {
$res['php'] = '';
}
/* TopTemplate: (Comment | If | Require | CacheBlock | UncachedBlock | OldI18NTag | ClosedBlock | OpenBlock | MalformedBlock | MismatchedEndBlock | Injection | Text)+ */
protected $match_TopTemplate_typestack = array('TopTemplate','Template');
function match_TopTemplate ($stack = array()) {
$matchrule = "TopTemplate"; $result = $this->construct($matchrule, $matchrule, array('TemplateMatcher' => 'Template'));
$count = 0;
while (true) {
$res_634 = $result;
$pos_634 = $this->pos;
$_633 = NULL;
do {
$_631 = NULL;
do {
$res_588 = $result;
$pos_588 = $this->pos;
$matcher = 'match_'.'Comment'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_631 = TRUE; break;
}
$result = $res_588;
$this->pos = $pos_588;
$_629 = NULL;
do {
$res_590 = $result;
$pos_590 = $this->pos;
$matcher = 'match_'.'If'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_629 = TRUE; break;
}
$result = $res_590;
$this->pos = $pos_590;
$_627 = NULL;
do {
$res_592 = $result;
$pos_592 = $this->pos;
$matcher = 'match_'.'Require'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_627 = TRUE; break;
}
$result = $res_592;
$this->pos = $pos_592;
$_625 = NULL;
do {
$res_594 = $result;
$pos_594 = $this->pos;
$matcher = 'match_'.'CacheBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_625 = TRUE; break;
}
$result = $res_594;
$this->pos = $pos_594;
$_623 = NULL;
do {
$res_596 = $result;
$pos_596 = $this->pos;
$matcher = 'match_'.'UncachedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_623 = TRUE; break;
}
$result = $res_596;
$this->pos = $pos_596;
$_621 = NULL;
do {
$res_598 = $result;
$pos_598 = $this->pos;
$matcher = 'match_'.'OldI18NTag'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_621 = TRUE; break;
}
$result = $res_598;
$this->pos = $pos_598;
$_619 = NULL;
do {
$res_600 = $result;
$pos_600 = $this->pos;
$matcher = 'match_'.'ClosedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_619 = TRUE; break;
}
$result = $res_600;
$this->pos = $pos_600;
$_617 = NULL;
do {
$res_602 = $result;
$pos_602 = $this->pos;
$matcher = 'match_'.'OpenBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_617 = TRUE; break;
}
$result = $res_602;
$this->pos = $pos_602;
$_615 = NULL;
do {
$res_604 = $result;
$pos_604 = $this->pos;
$matcher = 'match_'.'MalformedBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_615 = TRUE; break;
}
$result = $res_604;
$this->pos = $pos_604;
$_613 = NULL;
do {
$res_606 = $result;
$pos_606 = $this->pos;
$matcher = 'match_'.'MismatchedEndBlock'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_613 = TRUE; break;
}
$result = $res_606;
$this->pos = $pos_606;
$_611 = NULL;
do {
$res_608 = $result;
$pos_608 = $this->pos;
$matcher = 'match_'.'Injection'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_611 = TRUE; break;
}
$result = $res_608;
$this->pos = $pos_608;
$matcher = 'match_'.'Text'; $key = $matcher; $pos = $this->pos;
$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );
if ($subres !== FALSE) {
$this->store( $result, $subres );
$_611 = TRUE; break;
}
$result = $res_608;
$this->pos = $pos_608;
$_611 = FALSE; break;
}
while(0);
if( $_611 === TRUE ) { $_613 = TRUE; break; }
$result = $res_606;
$this->pos = $pos_606;
$_613 = FALSE; break;
}
while(0);
if( $_613 === TRUE ) { $_615 = TRUE; break; }
$result = $res_604;
$this->pos = $pos_604;
$_615 = FALSE; break;
}
while(0);
if( $_615 === TRUE ) { $_617 = TRUE; break; }
$result = $res_602;
$this->pos = $pos_602;
$_617 = FALSE; break;
}
while(0);
if( $_617 === TRUE ) { $_619 = TRUE; break; }
$result = $res_600;
$this->pos = $pos_600;
$_619 = FALSE; break;
}
while(0);
if( $_619 === TRUE ) { $_621 = TRUE; break; }
$result = $res_598;
$this->pos = $pos_598;
$_621 = FALSE; break;
}
while(0);
if( $_621 === TRUE ) { $_623 = TRUE; break; }
$result = $res_596;
$this->pos = $pos_596;
$_623 = FALSE; break;
}
while(0);
if( $_623 === TRUE ) { $_625 = TRUE; break; }
$result = $res_594;
$this->pos = $pos_594;
$_625 = FALSE; break;
}
while(0);
if( $_625 === TRUE ) { $_627 = TRUE; break; }
$result = $res_592;
$this->pos = $pos_592;
$_627 = FALSE; break;
}
while(0);
if( $_627 === TRUE ) { $_629 = TRUE; break; }
$result = $res_590;
$this->pos = $pos_590;
$_629 = FALSE; break;
}
while(0);
if( $_629 === TRUE ) { $_631 = TRUE; break; }
$result = $res_588;
$this->pos = $pos_588;
$_631 = FALSE; break;
}
while(0);
if( $_631 === FALSE) { $_633 = FALSE; break; }
$_633 = TRUE; break;
}
while(0);
if( $_633 === FALSE) {
$result = $res_634;
$this->pos = $pos_634;
unset( $res_634 );
unset( $pos_634 );
break;
}
$count += 1;
}
if ($count > 0) { return $this->finalise($result); }
else { return FALSE; }
}
/**
* The TopTemplate also includes the opening stanza to start off the template
*/
function TopTemplate__construct(&$res) {
$res['php'] = "<?php" . PHP_EOL;
}
/* Text: /
(
(\\.) | # Any escaped character
([^<${]) | # Any character that isn't <, $ or {
(<[^%]) | # < if not followed by %
($[^A-Za-z_]) | # $ if not followed by A-Z, a-z or _
({[^$]) | # { if not followed by $
({$[^A-Za-z_]) # {$ if not followed A-Z, a-z or _
)+
/ */
protected $match_Text_typestack = array('Text');
function match_Text ($stack = array()) {
$matchrule = "Text"; $result = $this->construct($matchrule, $matchrule, null);
if (( $subres = $this->rx( '/
(
(\\\\.) | # Any escaped character
([^<${]) | # Any character that isn\'t <, $ or {
(<[^%]) | # < if not followed by %
($[^A-Za-z_]) | # $ if not followed by A-Z, a-z or _
({[^$]) | # { if not followed by $
({$[^A-Za-z_]) # {$ if not followed A-Z, a-z or _
)+
/' ) ) !== FALSE) {
$result["text"] .= $subres;
return $this->finalise($result);
}
else { return FALSE; }
}
/**
* We convert text
*/
function Text__finalise(&$res) {
$text = $res['text'];
// TODO: This is _super_ ugly, and a performance killer to boot.
$text = preg_replace(
'/href\s*\=\s*\"\#/',
'href="' . PHP_EOL .
'SSVIEWER;' . PHP_EOL .
'$val .= SSViewer::$options[\'rewriteHashlinks\'] ? Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "";' . PHP_EOL .
'$val .= <<<SSVIEWER' . PHP_EOL .
'#',
$text
);
// TODO: using heredocs means any left over $ symbols will trigger PHP lookups, as will any escapes
// Will it break backwards compatibility to use ' quoted strings, and escape just the ' characters?
$res['php'] .=
'$val .= <<<SSVIEWER' . PHP_EOL .
$text . PHP_EOL .
'SSVIEWER;' . PHP_EOL ;
}
/******************
* Here ends the parser itself. Below are utility methods to use the parser
*/
/**
* Compiles some passed template source code into the php code that will execute as per the template source.
*
* @static
* @throws SSTemplateParseException
* @param $string - The source of the template
* @param string $templateName - The name of the template, normally the filename the template source was loaded from
* @param bool $includeDebuggingComments - True is debugging comments should be included in the output
* @return mixed|string - The php that, when executed (via include or exec) will behave as per the template source
*/
static function compileString($string, $templateName = "", $includeDebuggingComments=false) {
// Construct a parser instance
$parser = new SSTemplateParser($string);
$parser->includeDebuggingComments = $includeDebuggingComments;
// Ignore UTF8 BOM at begining of string. TODO: Confirm this is needed, make sure SSViewer handles UTF (and other encodings) properly
if(substr($string, 0,3) == pack("CCC", 0xef, 0xbb, 0xbf)) $parser->pos = 3;
// Match the source against the parser
$result = $parser->match_TopTemplate();
if(!$result) throw new SSTemplateParseException('Unexpected problem parsing template', $parser);
// Get the result
$code = $result['php'];
// Include top level debugging comments if desired
if($includeDebuggingComments && $templateName && stripos($code, "<?xml") === false) {
// If this template is a full HTML page, then put the comments just inside the HTML tag to prevent any IE glitches
if(stripos($code, "<html") !== false) {
$code = preg_replace('/(<html[^>]*>)/i', "\\1<!-- template $templateName -->", $code);
$code = preg_replace('/(<\/html[^>]*>)/i', "<!-- end template $templateName -->\\1", $code);
} else {
$code = "<!-- template $templateName -->\n" . $code . "\n<!-- end template $templateName -->";
}
}
return $code;
}
/**
* Compiles some file that contains template source code, and returns the php code that will execute as per that
* source
*
* @static
* @param $template - A file path that contains template source code
* @return mixed|string - The php that, when executed (via include or exec) will behave as per the template source
*/
static function compileFile($template) {
return self::compileString(file_get_contents($template), $template);
}
}

View File

@ -0,0 +1,937 @@
<?php
/*!* !insert_autogen_warning */
/*!* !silent
This is the uncompiled parser for the SilverStripe template language, PHP with special comments that define the parser.
It gets run through the php-peg parser compiler to have those comments turned into code that match parts of the template language,
producing the executable version SSTemplateParser.php
To recompile after changing this file, run this from the 'sapphire/core' directory via command line:
php ../thirdparty/php-peg/cli.php SSTemplateParser.php.inc > SSTemplateParser.php
See the php-peg docs for more information on the parser format, and how to convert this file into SSTemplateParser.php
TODO:
Template comments - <%-- --%>
$Iteration
Partial cache blocks
i18n - we dont support then deprecated _t() or sprintf(_t()) methods; or the new <% t %> block yet
Add with and loop blocks
Add Up and Top
More error detection?
This comment will not appear in the output
*/
// We want this to work when run by hand too
if (defined(THIRDPARTY_PATH)) {
require THIRDPARTY_PATH . '/php-peg/Parser.php' ;
}
else {
$base = dirname(__FILE__);
require $base.'/../thirdparty/php-peg/Parser.php';
}
/**
This is the exception raised when failing to parse a template. Note that we don't currently do any static analysis, so we can't know
if the template will run, just if it's malformed. It also won't catch mistakes that still look valid.
*/
class SSTemplateParseException extends Exception {
function __construct($message, $parser) {
$prior = substr($parser->string, 0, $parser->pos);
preg_match_all('/\r\n|\r|\n/', $prior, $matches);
$line = count($matches[0])+1;
parent::__construct("Parse error in template on line $line. Error was: $message");
}
}
/**
This is the parser for the SilverStripe template language. It gets called on a string and uses a php-peg parser to match
that string against the language structure, building up the PHP code to execute that structure as it parses
The $result array that is built up as part of the parsing (see thirdparty/php-peg/README.md for more on how parsers
build results) has one special member, 'php', which contains the php equivalent of that part of the template tree.
Some match rules generate alternate php, or other variations, so check the per-match documentation too.
Terms used:
Marked: A string or lookup in the template that has been explictly marked as such - lookups by prepending with "$"
(like $Foo.Bar), strings by wrapping with single or double quotes ('Foo' or "Foo")
Bare: The opposite of marked. An argument that has to has it's type inferred by usage and 2.4 defaults.
Example of using a bare argument for a loop block: <% loop Foo %>
Block: One of two SS template structures. The special characters "<%" and "%>" are used to wrap the opening and
(required or forbidden depending on which block exactly) closing block marks.
Open Block: An SS template block that doesn't wrap any content or have a closing end tag (in fact, a closing end tag is
forbidden)
Closed Block: An SS template block that wraps content, and requires a counterpart <% end_blockname %> tag
*/
class SSTemplateParser extends Parser {
/**
* @var bool - Set true by SSTemplateParser::compileString if the template should include comments intended
* for debugging (template source, included files, etc)
*/
protected $includeDebuggingComments = false;
/**
* Override the function that constructs the result arrays to also prepare a 'php' item in the array
*/
function construct($matchrule, $name, $arguments = null) {
$res = parent::construct($matchrule, $name, $arguments);
if (!isset($res['php'])) $res['php'] = '';
return $res;
}
/*!* SSTemplateParser
# Template is any structurally-complete portion of template (a full nested level in other words). It's the primary matcher,
# and is used by all enclosing blocks, as well as a base for the top level
Template: (Comment | If | Require | CacheBlock | UncachedBlock | OldI18NTag | ClosedBlock | OpenBlock | MalformedBlock | Injection | Text)+
*/
function Template_STR(&$res, $sub) {
$res['php'] .= $sub['php'] . PHP_EOL ;
}
/*!*
Word: / [A-Za-z_] [A-Za-z0-9_]* /
Number: / [0-9]+ /
Value: / [A-Za-z0-9_]+ /
# CallArguments is a list of one or more comma seperated "arguments" (lookups or strings, either bare or marked)
# as passed to a Call within brackets
CallArguments: :Argument ( < "," < :Argument )*
*/
/**
* Values are bare words in templates, but strings in PHP. We rely on PHP's type conversion to back-convert strings
* to numbers when needed.
*/
function CallArguments_Argument(&$res, $sub) {
if (!empty($res['php'])) $res['php'] .= ', ';
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : str_replace('$$FINAL', 'XML_val', $sub['php']);
}
/*!*
# Call is a php-style function call, e.g. Method(Argument, ...). Unlike PHP, the brackets are optional if no
# arguments are passed
Call: Method:Word ( "(" < :CallArguments? > ")" )?
# A lookup is a lookup of a value on the current scope object. It's a sequence of calls seperated by "." characters
# This final call in the sequence needs handling specially, as different structures need different sorts of values,
# which require a different final method to be called to get the right return value
LookupStep: :Call &"."
LastLookupStep: :Call
Lookup: LookupStep ("." LookupStep)* "." LastLookupStep | LastLookupStep
*/
function Lookup__construct(&$res) {
$res['php'] = '$scope';
$res['LookupSteps'] = array();
}
/**
* The basic generated PHP of LookupStep and LastLookupStep is the same, except that LookupStep calls 'obj' to
* get the next ViewableData in the sequence, and LastLookupStep calls different methods (XML_val, hasValue, obj)
* depending on the context the lookup is used in.
*/
function Lookup_AddLookupStep(&$res, $sub, $method) {
$res['LookupSteps'][] = $sub;
$property = $sub['Call']['Method']['text'];
if (isset($sub['Call']['CallArguments']) && $arguments = $sub['Call']['CallArguments']['php']) {
$res['php'] .= "->$method('$property', array($arguments), true)";
}
else {
$res['php'] .= "->$method('$property', null, true)";
}
}
function Lookup_LookupStep(&$res, $sub) {
$this->Lookup_AddLookupStep($res, $sub, 'obj');
}
function Lookup_LastLookupStep(&$res, $sub) {
$this->Lookup_AddLookupStep($res, $sub, '$$FINAL');
}
/*!*
# Injections are where, outside of a block, a value needs to be inserted into the output. You can either
# just do $Foo, or {$Foo} if the surrounding text would cause a problem (e.g. {$Foo}Bar)
SimpleInjection: '$' :Lookup
BracketInjection: '{$' :Lookup "}"
Injection: BracketInjection | SimpleInjection
*/
function Injection_STR(&$res, $sub) {
$res['php'] = '$val .= '. str_replace('$$FINAL', 'XML_val', $sub['Lookup']['php']) . ';';
}
/*!*
# Inside a block's arguments you can still use the same format as a simple injection ($Foo). In this case
# it marks the argument as being a lookup, not a string (if it was bare it might still be used as a lookup,
# but that depends on where it's used, a la 2.4)
DollarMarkedLookup: SimpleInjection
*/
function DollarMarkedLookup_STR(&$res, $sub) {
$res['Lookup'] = $sub['Lookup'];
}
/*!*
# Inside a block's arguments you can explictly mark a string by surrounding it with quotes (single or double,
# but they must be matching). If you do, inside the quote you can escape any character, but the only character
# that _needs_ escaping is the matching closing quote
QuotedString: q:/['"]/ String:/ (\\\\ | \\. | [^$q\\])* / '$q'
# In order to support 2.4's base syntax, we also need to detect free strings - strings not surrounded by
# quotes, and containing spaces or punctuation, but supported as a single string. We support almost as flexible
# a string as 2.4 - we don't attempt to determine the closing character by context, but just break on any character
# which, in some context, would indicate the end of a free string, regardless of if we're actually in that context
# or not
FreeString: /[^,)%!=|&]+/
# An argument - either a marked value, or a bare value, prefering lookup matching on the bare value over freestring
# matching as long as that would give a successful parse
Argument:
:DollarMarkedLookup |
:QuotedString |
:Lookup !(< FreeString)|
:FreeString
*/
/**
* If we get a bare value, we don't know enough to determine exactly what php would be the translation, because
* we don't know if the position of use indicates a lookup or a string argument.
*
* Instead, we record 'ArgumentMode' as a member of this matches results node, which can be:
* - lookup if this argument was unambiguously a lookup (marked as such)
* - string is this argument was unambiguously a string (marked as such, or impossible to parse as lookup)
* - default if this argument needs to be handled as per 2.4
*
* In the case of 'default', there is no php member of the results node, but instead 'lookup_php', which
* should be used by the parent if the context indicates a lookup, and 'string_php' which should be used
* if the context indicates a string
*/
function Argument_DollarMarkedLookup(&$res, $sub) {
$res['ArgumentMode'] = 'lookup';
$res['php'] = $sub['Lookup']['php'];
}
function Argument_QuotedString(&$res, $sub) {
$res['ArgumentMode'] = 'string';
$res['php'] = "'" . str_replace("'", "\\'", $sub['String']['text']) . "'";
}
function Argument_Lookup(&$res, $sub) {
if (count($sub['LookupSteps']) == 1 && !isset($sub['LookupSteps'][0]['Call']['Arguments'])) {
$res['ArgumentMode'] = 'default';
$res['lookup_php'] = $sub['php'];
$res['string_php'] = "'".$sub['LookupSteps'][0]['Call']['Method']['text']."'";
}
else {
$res['ArgumentMode'] = 'lookup';
$res['php'] = $sub['php'];
}
}
function Argument_FreeString(&$res, $sub) {
$res['ArgumentMode'] = 'string';
$res['php'] = "'" . str_replace("'", "\\'", $sub['text']) . "'";
}
/*!*
# if and else_if blocks allow basic comparisons between arguments
ComparisonOperator: "==" | "!=" | "="
Comparison: Argument < ComparisonOperator > Argument
*/
function Comparison_Argument(&$res, $sub) {
if ($sub['ArgumentMode'] == 'default') {
if (!empty($res['php'])) $res['php'] .= $sub['string_php'];
else $res['php'] = str_replace('$$FINAL', 'XML_val', $sub['lookup_php']);
}
else {
$res['php'] .= str_replace('$$FINAL', 'XML_val', $sub['php']);
}
}
function Comparison_ComparisonOperator(&$res, $sub) {
$res['php'] .= ($sub['text'] == '=' ? '==' : $sub['text']);
}
/*!*
# If a comparison operator is not used in an if or else_if block, then the statement is a 'presence check',
# which checks if the argument given is present or not. For explicit strings (which were not allowed in 2.4)
# this falls back to simple truthiness check
PresenceCheck: (Not:'not' <)? Argument
*/
function PresenceCheck_Not(&$res, $sub) {
$res['php'] = '!';
}
function PresenceCheck_Argument(&$res, $sub) {
if ($sub['ArgumentMode'] == 'string') {
$res['php'] .= '((bool)'.$sub['php'].')';
}
else {
$php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']);
// TODO: kinda hacky - maybe we need a way to pass state down the parse chain so
// Lookup_LastLookupStep and Argument_BareWord can produce hasValue instead of XML_val
$res['php'] .= str_replace('$$FINAL', 'hasValue', $php);
}
}
/*!*
# if and else_if arguments are a series of presence checks and comparisons, optionally seperated by boolean
# operators
IfArgumentPortion: Comparison | PresenceCheck
*/
function IfArgumentPortion_STR(&$res, $sub) {
$res['php'] = $sub['php'];
}
/*!*
# if and else_if arguments can be combined via these two boolean operators. No precendence overriding is supported
BooleanOperator: "||" | "&&"
# This is the combination of the previous if and else_if argument portions
IfArgument: :IfArgumentPortion ( < :BooleanOperator < :IfArgumentPortion )*
*/
function IfArgument_IfArgumentPortion(&$res, $sub) {
$res['php'] .= $sub['php'];
}
function IfArgument_BooleanOperator(&$res, $sub) {
$res['php'] .= $sub['text'];
}
/*!*
# ifs are handled seperately from other closed block tags, because (A) their structure is different - they
# can have else_if and else tags in between the if tag and the end_if tag, and (B) they have a different
# argument structure to every other block
IfPart: '<%' < 'if' [ :IfArgument > '%>' Template:$TemplateMatcher?
ElseIfPart: '<%' < 'else_if' [ :IfArgument > '%>' Template:$TemplateMatcher
ElsePart: '<%' < 'else' > '%>' Template:$TemplateMatcher
If: IfPart ElseIfPart* ElsePart? '<%' < 'end_if' > '%>'
*/
function If_IfPart(&$res, $sub) {
$res['php'] =
'if (' . $sub['IfArgument']['php'] . ') { ' . PHP_EOL .
(isset($sub['Template']) ? $sub['Template']['php'] : '') . PHP_EOL .
'}';
}
function If_ElseIfPart(&$res, $sub) {
$res['php'] .=
'else if (' . $sub['IfArgument']['php'] . ') { ' . PHP_EOL .
$sub['Template']['php'] . PHP_EOL .
'}';
}
function If_ElsePart(&$res, $sub) {
$res['php'] .=
'else { ' . PHP_EOL .
$sub['Template']['php'] . PHP_EOL .
'}';
}
/*!*
# The require block is handled seperately to the other open blocks as the argument syntax is different
# - must have one call style argument, must pass arguments to that call style argument
Require: '<%' < 'require' [ Call:(Method:Word "(" < :CallArguments > ")") > '%>'
*/
function Require_Call(&$res, $sub) {
$res['php'] = "Requirements::".$sub['Method']['text'].'('.$sub['CallArguments']['php'].');';
}
/*!*
# Cache block arguments don't support free strings
CacheBlockArgument:
!( "if " | "unless " )
(
:DollarMarkedLookup |
:QuotedString |
:Lookup
)
*/
function CacheBlockArgument_DollarMarkedLookup(&$res, $sub) {
$res['php'] = $sub['Lookup']['php'];
}
function CacheBlockArgument_QuotedString(&$res, $sub) {
$res['php'] = "'" . str_replace("'", "\\'", $sub['String']['text']) . "'";
}
function CacheBlockArgument_Lookup(&$res, $sub) {
$res['php'] = $sub['php'];
}
/*!*
# Collects the arguments passed in to be part of the key of a cacheblock
CacheBlockArguments: CacheBlockArgument ( < "," < CacheBlockArgument )*
*/
function CacheBlockArguments_CacheBlockArgument(&$res, $sub) {
if (!empty($res['php'])) $res['php'] .= ".'_'.";
else $res['php'] = '';
$res['php'] .= str_replace('$$FINAL', 'XML_val', $sub['php']);
}
/*!*
# CacheBlockTemplate is the same as Template, but doesn't include cache blocks (because they're handled seperately)
CacheBlockTemplate extends Template (TemplateMatcher = CacheRestrictedTemplate); CacheBlock | UncachedBlock | => ''
*/
/*!*
UncachedBlock:
'<%' < "uncached" < CacheBlockArguments? ( < Conditional:("if"|"unless") > Condition:IfArgument )? > '%>'
Template:$TemplateMatcher?
'<%' < 'end_' ("uncached"|"cached"|"cacheblock") > '%>'
*/
function UncachedBlock_Template(&$res, $sub){
$res['php'] = $sub['php'];
}
/*!*
# CacheRestrictedTemplate is the same as Template, but doesn't allow cache blocks
CacheRestrictedTemplate extends Template
*/
function CacheRestrictedTemplate_CacheBlock(&$res, $sub) {
throw new SSTemplateParseException('You cant have cache blocks nested within with, loop or control blocks that are within cache blocks', $this);
}
function CacheRestrictedTemplate_UncachedBlock(&$res, $sub) {
throw new SSTemplateParseException('You cant have uncache blocks nested within with, loop or control blocks that are within cache blocks', $this);
}
/*!*
# The partial caching block
CacheBlock:
'<%' < CacheTag:("cached"|"cacheblock") < (CacheBlockArguments)? ( < Conditional:("if"|"unless") > Condition:IfArgument )? > '%>'
(CacheBlock | UncachedBlock | CacheBlockTemplate)*
'<%' < 'end_' ("cached"|"uncached"|"cacheblock") > '%>'
*/
function CacheBlock__construct(&$res){
$res['subblocks'] = 0;
}
function CacheBlock_CacheBlockArguments(&$res, $sub){
$res['key'] = !empty($sub['php']) ? $sub['php'] : '';
}
function CacheBlock_Condition(&$res, $sub){
$res['condition'] = ($res['Conditional']['text'] == 'if' ? '(' : '!(') . $sub['php'] . ') && ';
}
function CacheBlock_CacheBlock(&$res, $sub){
$res['php'] .= $sub['php'];
}
function CacheBlock_UncachedBlock(&$res, $sub){
$res['php'] .= $sub['php'];
}
function CacheBlock_CacheBlockTemplate(&$res, $sub){
// Get the block counter
$block = ++$res['subblocks'];
// Build the key for this block from the passed cache key, the block index, and the sha hash of the template itself
$key = "'" . sha1($sub['php']) . (isset($res['key']) && $res['key'] ? "_'.sha1(".$res['key'].")" : "'") . ".'_$block'";
// Get any condition
$condition = isset($res['condition']) ? $res['condition'] : '';
$res['php'] .= 'if ('.$condition.'($partial = $cache->load('.$key.'))) $val .= $partial;' . PHP_EOL;
$res['php'] .= 'else { $oldval = $val; $val = "";' . PHP_EOL;
$res['php'] .= $sub['php'] . PHP_EOL;
$res['php'] .= $condition . ' $cache->save($val); $val = $oldval . $val;' . PHP_EOL;
$res['php'] .= '}';
}
/*!*
# Deprecated old-style i18n _t and sprintf(_t block tags. We support a slightly more flexible version than we used
# to, but just because it's easier to do so. It's strongly recommended to use the new syntax
# This is the core used by both syntaxes, without the block start & end tags
OldTPart: "_t" < "(" < QuotedString (< "," < CallArguments)? > ")"
*/
function OldTPart__construct(&$res) {
$res['php'] = "_t(";
}
function OldTPart_QuotedString(&$res, $sub) {
$entity = $sub['String']['text'];
if (strpos($entity, '.') === false) {
$res['php'] .= "\$scope->XML_val('I18NNamespace').'.$entity'";
}
else {
$res['php'] .= "'$entity'";
}
}
function OldTPart_CallArguments(&$res, $sub) {
$res['php'] .= ',' . $sub['php'];
}
function OldTPart__finalise(&$res) {
$res['php'] .= ')';
}
/*!*
# This is the old <% _t() %> tag
OldTTag: "<%" < OldTPart > "%>"
*/
function OldTTag_OldTPart(&$res, $sub) {
$res['php'] = $sub['php'];
}
/*!*
# This is the old <% sprintf(_t()) %> tag
OldSprintfTag: "<%" < "sprintf" < "(" < OldTPart < "," < CallArguments > ")" > "%>"
*/
function OldSprintfTag__construct(&$res) {
$res['php'] = "sprintf(";
}
function OldSprintfTag_OldTPart(&$res, $sub) {
$res['php'] .= $sub['php'];
}
function OldSprintfTag_CallArguments(&$res, $sub) {
$res['php'] .= ',' . $sub['php'] . ')';
}
/*!*
# This matches either the old style sprintf(_t()) or _t() tags. As well as including the output portion of the
# php, this rule combines all the old i18n stuff into a single match rule to make it easy to not support these tags later
OldI18NTag: OldSprintfTag | OldTTag
*/
function OldI18NTag_STR(&$res, $sub) {
$res['php'] = '$val .= ' . $sub['php'] . ';';
}
/*!*
# To make the block support reasonably extendable, we don't explicitly define each closed block and it's structure,
# but instead match against a generic <% block_name argument, ... %> pattern. Each argument is left as per the
# output of the Argument matcher, and the handler (see the PHPDoc block later for more on this) is responsible
# for pulling out the info required
BlockArguments: :Argument ( < "," < :Argument)*
# NotBlockTag matches against any word that might come after a "<%" that the generic open and closed block handlers
# shouldn't attempt to match against, because they're handled by more explicit matchers
NotBlockTag: "end_" | (("if" | "else_if" | "else" | "require" | "cached" | "uncached" | "cacheblock") ] )
# Match against closed blocks - blocks with an opening and a closing tag that surround some internal portion of
# template
ClosedBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > Zap:'%>' Template:$TemplateMatcher? '<%' < 'end_' '$BlockName' > '%>'
*/
/**
* As mentioned in the parser comment, block handling is kept fairly generic for extensibility. The match rule
* builds up two important elements in the match result array:
* 'ArgumentCount' - how many arguments were passed in the opening tag
* 'Arguments' an array of the Argument match rule result arrays
*
* Once a block has successfully been matched against, it will then look for the actual handler, which should
* be on this class (either defined or decorated on) as ClosedBlock_Handler_Name(&$res), where Name is the
* tag name, first letter captialized (i.e Control, Loop, With, etc).
*
* This function will be called with the match rule result array as it's first argument. It should return
* the php result of this block as it's return value, or throw an error if incorrect arguments were passed.
*/
function ClosedBlock__construct(&$res) {
$res['ArgumentCount'] = 0;
}
function ClosedBlock_BlockArguments(&$res, $sub) {
if (isset($sub['Argument']['ArgumentMode'])) {
$res['Arguments'] = array($sub['Argument']);
$res['ArgumentCount'] = 1;
}
else {
$res['Arguments'] = $sub['Argument'];
$res['ArgumentCount'] = count($res['Arguments']);
}
}
function ClosedBlock__finalise(&$res) {
$blockname = $res['BlockName']['text'];
$method = 'ClosedBlock_Handle_'.ucfirst(strtolower($blockname));
if (method_exists($this, $method)) $res['php'] = $this->$method($res);
else {
throw new SSTemplateParseException('Unknown closed block "'.$blockname.'" encountered. Perhaps you are not supposed to close this block, or have mis-spelled it?', $this);
}
}
/**
* This is an example of a block handler function. This one handles the loop tag.
*/
function ClosedBlock_Handle_Loop(&$res) {
if ($res['ArgumentCount'] != 1) {
throw new SSTemplateParseException('Either no or too many arguments in control block. Must be one argument only.', $this);
}
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') {
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
}
$on = str_replace('$$FINAL', 'obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
return
$on . '; $scope->pushScope(); while (($key = $scope->next()) !== false) {' . PHP_EOL .
$res['Template']['php'] . PHP_EOL .
'}; $scope->popScope(); ';
}
/**
* The deprecated closed block handler for control blocks
* @deprecated
*/
function ClosedBlock_Handle_Control(&$res) {
return $this->ClosedBlock_Handle_Loop($res);
}
/**
* The closed block handler for with blocks
*/
function ClosedBlock_Handle_With(&$res) {
if ($res['ArgumentCount'] != 1) {
throw new SSTemplateParseException('Either no or too many arguments in with block. Must be one argument only.', $this);
}
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') {
throw new SSTemplateParseException('Control block cant take string as argument.', $this);
}
$on = str_replace('$$FINAL', 'obj', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']);
return
$on . '; $scope->pushScope();' . PHP_EOL .
$res['Template']['php'] . PHP_EOL .
'; $scope->popScope(); ';
}
/*!*
# Open blocks are handled in the same generic manner as closed blocks. There is no need to define which blocks
# are which - closed is tried first, and if no matching end tag is found, open is tried next
OpenBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > '%>'
*/
function OpenBlock__construct(&$res) {
$res['ArgumentCount'] = 0;
}
function OpenBlock_BlockArguments(&$res, $sub) {
if (isset($sub['Argument']['ArgumentMode'])) {
$res['Arguments'] = array($sub['Argument']);
$res['ArgumentCount'] = 1;
}
else {
$res['Arguments'] = $sub['Argument'];
$res['ArgumentCount'] = count($res['Arguments']);
}
}
function OpenBlock__finalise(&$res) {
$blockname = $res['BlockName']['text'];
$method = 'OpenBlock_Handle_'.ucfirst(strtolower($blockname));
if (method_exists($this, $method)) $res['php'] = $this->$method($res);
else {
throw new SSTemplateParseException('Unknown open block "'.$blockname.'" encountered. Perhaps you missed the closing tag or have mis-spelled it?', $this);
}
}
/**
* This is an open block handler, for the <% include %> tag
*/
function OpenBlock_Handle_Include(&$res) {
if ($res['ArgumentCount'] != 1) throw new SSTemplateParseException('Include takes exactly one argument', $this);
$arg = $res['Arguments'][0];
$php = ($arg['ArgumentMode'] == 'default') ? $arg['string_php'] : $arg['php'];
if($this->includeDebuggingComments) { // Add include filename comments on dev sites
return
'$val .= \'<!-- include '.$php.' -->\';'. "\n".
'$val .= SSViewer::parse_template('.$php.', $scope->getItem());'. "\n".
'$val .= \'<!-- end include '.$php.' -->\';'. "\n";
}
else {
return
'$val .= SSViewer::execute_template('.$php.', $scope->getItem());'. "\n";
}
}
/**
* This is an open block handler, for the <% debug %> utility tag
*/
function OpenBlock_Handle_Debug(&$res) {
if ($res['ArgumentCount'] == 0) return '$scope->debug();';
else if ($res['ArgumentCount'] == 1) {
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') return 'Debug::show('.$arg['php'].');';
$php = ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php'];
return '$val .= Debug::show('.str_replace('FINALGET!', 'cachedCall', $php).');';
}
else {
throw new SSTemplateParseException('Debug takes 0 or 1 argument only.', $this);
}
}
/**
* This is an open block handler, for the <% base_tag %> tag
*/
function OpenBlock_Handle_Base_tag(&$res) {
if ($res['ArgumentCount'] != 0) throw new SSTemplateParseException('Base_tag takes no arguments', $this);
return '$val .= SSViewer::get_base_tag($val);';
}
/**
* This is an open block handler, for the <% current_page %> tag
*/
function OpenBlock_Handle_Current_page(&$res) {
if ($res['ArgumentCount'] != 0) throw new SSTemplateParseException('Current_page takes no arguments', $this);
return '$val .= $_SERVER[SCRIPT_URL];';
}
/*!*
# This is used to detect when we have a mismatched closing tag (i.e., one with no equivilent opening tag)
# Because of parser limitations, this can only be used at the top nesting level of a template. Other mismatched
# closing tags are detected as an invalid open tag
MismatchedEndBlock: '<%' < 'end_' :Word > '%>'
*/
function MismatchedEndBlock__finalise(&$res) {
$blockname = $res['Word']['text'];
throw new SSTemplateParseException('Unexpected close tag end_'.$blockname.' encountered. Perhaps you have mis-nested blocks, or have mis-spelled a tag?', $this);
}
/*!*
# This is used to detect a malformed opening tag - one where the tag is opened with the "<%" characters, but
# the tag is not structured properly
MalformedOpenTag: '<%' < !NotBlockTag Tag:Word !( ( [ :BlockArguments ] )? > '%>' )
*/
function MalformedOpenTag__finalise(&$res) {
$tag = $res['Tag']['text'];
throw new SSTemplateParseException("Malformed opening block tag $tag. Perhaps you have tried to use operators?", $this);
}
/*!*
# This is used to detect a malformed end tag - one where the tag is opened with the "<%" characters, but
# the tag is not structured properly
MalformedCloseTag: '<%' < Tag:('end_' :Word ) !( > '%>' )
*/
function MalformedCloseTag__finalise(&$res) {
$tag = $res['Tag']['text'];
throw new SSTemplateParseException("Malformed closing block tag $tag. Perhaps you have tried to pass an argument to one?", $this);
}
/*!*
# This is used to detect a malformed tag. It's mostly to keep the Template match rule a bit shorter
MalformedBlock: MalformedOpenTag | MalformedCloseTag
*/
/*!*
# This is used to remove template comments
Comment: "<%--" (!"--%>" /./)+ "--%>"
*/
function Comment__construct(&$res) {
$res['php'] = '';
}
/*!*
# TopTemplate is the same as Template, but should only be used at the top level (not nested), as it includes
# MismatchedEndBlock detection, which only works at the top level
TopTemplate extends Template (TemplateMatcher = Template); MalformedBlock => MalformedBlock | MismatchedEndBlock
*/
/**
* The TopTemplate also includes the opening stanza to start off the template
*/
function TopTemplate__construct(&$res) {
$res['php'] = "<?php" . PHP_EOL;
}
/*!*
# Text matches anything that isn't a template command (not an injection, block of any kind or comment)
Text: /
(
(\\.) | # Any escaped character
([^<${]) | # Any character that isn't <, $ or {
(<[^%]) | # < if not followed by %
($[^A-Za-z_]) | # $ if not followed by A-Z, a-z or _
({[^$]) | # { if not followed by $
({$[^A-Za-z_]) # {$ if not followed A-Z, a-z or _
)+
/
*/
/**
* We convert text
*/
function Text__finalise(&$res) {
$text = $res['text'];
// TODO: This is _super_ ugly, and a performance killer to boot.
$text = preg_replace(
'/href\s*\=\s*\"\#/',
'href="' . PHP_EOL .
'SSVIEWER;' . PHP_EOL .
'$val .= SSViewer::$options[\'rewriteHashlinks\'] ? Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "";' . PHP_EOL .
'$val .= <<<SSVIEWER' . PHP_EOL .
'#',
$text
);
// TODO: using heredocs means any left over $ symbols will trigger PHP lookups, as will any escapes
// Will it break backwards compatibility to use ' quoted strings, and escape just the ' characters?
$res['php'] .=
'$val .= <<<SSVIEWER' . PHP_EOL .
$text . PHP_EOL .
'SSVIEWER;' . PHP_EOL ;
}
/******************
* Here ends the parser itself. Below are utility methods to use the parser
*/
/**
* Compiles some passed template source code into the php code that will execute as per the template source.
*
* @static
* @throws SSTemplateParseException
* @param $string - The source of the template
* @param string $templateName - The name of the template, normally the filename the template source was loaded from
* @param bool $includeDebuggingComments - True is debugging comments should be included in the output
* @return mixed|string - The php that, when executed (via include or exec) will behave as per the template source
*/
static function compileString($string, $templateName = "", $includeDebuggingComments=false) {
// Construct a parser instance
$parser = new SSTemplateParser($string);
$parser->includeDebuggingComments = $includeDebuggingComments;
// Ignore UTF8 BOM at begining of string. TODO: Confirm this is needed, make sure SSViewer handles UTF (and other encodings) properly
if(substr($string, 0,3) == pack("CCC", 0xef, 0xbb, 0xbf)) $parser->pos = 3;
// Match the source against the parser
$result = $parser->match_TopTemplate();
if(!$result) throw new SSTemplateParseException('Unexpected problem parsing template', $parser);
// Get the result
$code = $result['php'];
// Include top level debugging comments if desired
if($includeDebuggingComments && $templateName && stripos($code, "<?xml") === false) {
// If this template is a full HTML page, then put the comments just inside the HTML tag to prevent any IE glitches
if(stripos($code, "<html") !== false) {
$code = preg_replace('/(<html[^>]*>)/i', "\\1<!-- template $templateName -->", $code);
$code = preg_replace('/(<\/html[^>]*>)/i', "<!-- end template $templateName -->\\1", $code);
} else {
$code = "<!-- template $templateName -->\n" . $code . "\n<!-- end template $templateName -->";
}
}
return $code;
}
/**
* Compiles some file that contains template source code, and returns the php code that will execute as per that
* source
*
* @static
* @param $template - A file path that contains template source code
* @return mixed|string - The php that, when executed (via include or exec) will behave as per the template source
*/
static function compileFile($template) {
return self::compileString(file_get_contents($template), $template);
}
}

View File

@ -1,4 +1,167 @@
<?php
/**
* This tracks the current scope for an SSViewer instance. It has three goals:
* - Handle entering & leaving sub-scopes in loops and withs
* - Track Up and Top
* - (As a side effect) Inject data that needs to be available globally (used to live in ViewableData)
*
* In order to handle up, rather than tracking it using a tree, which would involve constructing new objects
* for each step, we use indexes into the itemStack (which already has to exist).
*
* Each item has three indexes associated with it
*
* - Pop. Which item should become the scope once the current scope is popped out of
* - Up. Which item is up from this item
* - Current. Which item is the first time this object has appeared in the stack
*
* We also keep the index of the current starting point for lookups. A lookup is a sequence of obj calls -
* when in a loop or with tag the end result becomes the new scope, but for injections, we throw away the lookup
* and revert back to the original scope once we've got the value we're after
*
*/
class SSViewer_Scope {
// The stack of previous "global" items
// And array of item, itemIterator, pop_index, up_index, current_index
private $itemStack = array();
private $item; // The current "global" item (the one any lookup starts from)
private $itemIterator; // If we're looping over the current "global" item, here's the iterator that tracks with item we're up to
private $popIndex; // A pointer into the item stack for which item should be scope on the next pop call
private $upIndex; // A pointer into the item stack for which item is "up" from this one
private $currentIndex; // A pointer into the item stack for which item is this one (or null if not in stack yet)
private $localIndex;
function __construct($item){
$this->item = $item;
$this->localIndex=0;
$this->itemStack[] = array($this->item, null, null, null, 0);
}
function getItem(){
return $this->itemIterator ? $this->itemIterator->current() : $this->item;
}
function resetLocalScope(){
list($this->item, $this->itemIterator, $this->popIndex, $this->upIndex, $this->currentIndex) = $this->itemStack[$this->localIndex];
array_splice($this->itemStack, $this->localIndex+1);
}
function obj($name){
switch ($name) {
case 'Up':
list($this->item, $this->itemIterator, $unused2, $this->upIndex, $this->currentIndex) = $this->itemStack[$this->upIndex];
break;
case 'Top':
list($this->item, $this->itemIterator, $unused2, $this->upIndex, $this->currentIndex) = $this->itemStack[0];
break;
default:
$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
$this->item = call_user_func_array(array($on, 'obj'), func_get_args());
$this->itemIterator = null;
$this->upIndex = $this->currentIndex ? $this->currentIndex : count($this->itemStack)-1;
$this->currentIndex = count($this->itemStack);
break;
}
$this->itemStack[] = array($this->item, $this->itemIterator, null, $this->upIndex, $this->currentIndex);
return $this;
}
function pushScope(){
$newLocalIndex = count($this->itemStack)-1;
$this->popIndex = $this->itemStack[$newLocalIndex][2] = $this->localIndex;
$this->localIndex = $newLocalIndex;
// We normally keep any previous itemIterator around, so local $Up calls reference the right element. But
// once we enter a new global scope, we need to make sure we use a new one
$this->itemIterator = $this->itemStack[$newLocalIndex][1] = null;
return $this;
}
function popScope(){
$this->localIndex = $this->popIndex;
$this->resetLocalScope();
return $this;
}
function next(){
if (!$this->item) return false;
if (!$this->itemIterator) {
if (is_array($this->item)) $this->itemIterator = new ArrayIterator($this->item);
else $this->itemIterator = $this->item->getIterator();
$this->itemStack[$this->localIndex][1] = $this->itemIterator;
$this->itemIterator->rewind();
}
else {
$this->itemIterator->next();
}
$this->resetLocalScope();
if (!$this->itemIterator->valid()) return false;
return $this->itemIterator->key();
}
function __call($name, $arguments) {
$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
$retval = call_user_func_array(array($on, $name), $arguments);
$this->resetLocalScope();
return $retval;
}
}
/**
* This extends SSViewer_Scope to mix in data on top of what the item provides. This can be "global"
* data that is scope-independant (like BaseURL), or type-specific data that is layered on top cross-cut like
* (like $FirstLast etc).
*
* It's separate from SSViewer_Scope to keep that fairly complex code as clean as possible.
*/
class SSViewer_DataPresenter extends SSViewer_Scope {
private $extras;
function __construct($item, $extras = null){
parent::__construct($item);
$this->extras = $extras;
}
function __call($name, $arguments) {
$property = $arguments[0];
if ($this->extras && array_key_exists($property, $this->extras)) {
$this->resetLocalScope();
$value = $this->extras[$arguments[0]];
switch ($name) {
case 'hasValue':
return (bool)$value;
default:
return $value;
}
}
return parent::__call($name, $arguments);
}
}
/**
* Parses a template file with an *.ss file extension.
*
@ -422,14 +585,12 @@ class SSViewer {
}
}
$itemStack = array();
$scope = new SSViewer_DataPresenter($item, array('I18NNamespace' => basename($template)));
$val = "";
$valStack = array();
include($cacheFile);
$output = $val;
$output = Requirements::includeInHTML($template, $output);
$output = Requirements::includeInHTML($template, $val);
array_pop(SSViewer::$topLevel);
@ -460,174 +621,7 @@ class SSViewer {
}
static function parseTemplateContent($content, $template="") {
// Remove UTF-8 byte order mark:
// This is only necessary if you don't have zend-multibyte enabled.
if(substr($content, 0,3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
$content = substr($content, 3);
}
// Add template filename comments on dev sites
if(Director::isDev() && self::$source_file_comments && $template && stripos($content, "<?xml") === false) {
// If this template is a full HTML page, then put the comments just inside the HTML tag to prevent any IE glitches
if(stripos($content, "<html") !== false) {
$content = preg_replace('/(<html[^>]*>)/i', "\\1<!-- template $template -->", $content);
$content = preg_replace('/(<\/html[^>]*>)/i', "\\1<!-- end template $template -->", $content);
} else {
$content = "<!-- template $template -->\n" . $content . "\n<!-- end template $template -->";
}
}
// $val, $val.property, $val(param), etc.
$replacements = array(
'/<%--.*--%>/U' => '',
'/\$Iteration/' => '<?= {dlr}key ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->obj("\\4",null,true)->XML_val("\\5",null,true) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->XML_val("\\4",null,true) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->obj("\\3",null,true)->XML_val("\\4",null,true) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->XML_val("\\3",null,true) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2"),true) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",null,true)->obj("\\2",null,true)->XML_val("\\3",null,true) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",null,true)->XML_val("\\2",null,true) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)}/' => '<?= {dlr}item->XML_val("\\1",null,true) ?>\\2',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\(([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1")->XML_val("\\2",array("\\3"),true) ?>\\4',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\(([^),]+), *([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1")->XML_val("\\2",array("\\3", "\\4"),true) ?>\\5',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->obj("\\4",null,true)->XML_val("\\5",null,true) ?>\\6',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->XML_val("\\4",null,true) ?>\\5',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>\\4',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->obj("\\3",null,true)->XML_val("\\4",null,true) ?>\\5',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->XML_val("\\3",null,true) ?>\\4',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",array("\\2"),true) ?>\\3',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",null,true)->obj("\\2",null,true)->XML_val("\\3",null,true) ?>\\4',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",null,true)->XML_val("\\2",null,true) ?>\\3',
'/\\$([A-Za-z_][A-Za-z0-9_]*)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",null,true) ?>\\2',
);
$content = preg_replace(array_keys($replacements), array_values($replacements), $content);
$content = str_replace('{dlr}','$',$content);
// Cache block
$content = SSViewer_PartialParser::process($template, $content);
// legacy
$content = ereg_replace('<!-- +pc +([A-Za-z0-9_(),]+) +-->', '<' . '% control \\1 %' . '>', $content);
$content = ereg_replace('<!-- +pc_end +-->', '<' . '% end_control %' . '>', $content);
// < % control Foo % >
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1")) foreach($loop as $key => $item) { ?>', $content);
// < % control Foo.Bar % >
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if(($loop = $item->obj("\\1")) && ($loop = $loop->obj("\\2"))) foreach($loop as $key => $item) { ?>', $content);
// < % control Foo.Bar(Baz) % >
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if(($loop = $item->obj("\\1")) && ($loop = $loop->obj("\\2", array("\\3")))) foreach($loop as $key => $item) { ?>', $content);
// < % control Foo(Bar) % >
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2"))) foreach($loop as $key => $item) { ?>', $content);
// < % control Foo(Bar, Baz) % >
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([^),]+), *([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2","\\3"))) foreach($loop as $key => $item) { ?>', $content);
// < % control Foo(Bar, Baz, Buz) % >
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([^),]+), *([^),]+), *([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2", "\\3", "\\4"))) foreach($loop as $key => $item) { ?>', $content);
$content = ereg_replace('<' . '% +end_control +%' . '>', '<? } $item = array_pop($itemStack); ?>', $content);
$content = ereg_replace('<' . '% +debug +%' . '>', '<? Debug::show($item) ?>', $content);
$content = ereg_replace('<' . '% +debug +([A-Za-z0-9_]+) +%' . '>', '<? Debug::show($item->cachedCall("\\1")) ?>', $content);
// < % if val1.property % >
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? if($item->obj("\\1",null,true)->hasValue("\\2")) { ?>', $content);
// < % if val1(parameter) % >
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([A-Za-z0-9_-]+)\\) +%' . '>', '<? if($item->hasValue("\\1",array("\\2"))) { ?>', $content);
// < % if val1 % >
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1")) { ?>', $content);
$content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) +%' . '>', '<? } else if($item->hasValue("\\1")) { ?>', $content);
// < % if val1 || val2 % >
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1") || $item->hasValue("\\2")) { ?>', $content);
$content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>', '<? else_if($item->hasValue("\\1") || $item->hasValue("\\2")) { ?>', $content);
// < % if val1 && val2 % >
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1") && $item->hasValue("\\2")) { ?>', $content);
$content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>', '<? else_if($item->hasValue("\\1") && $item->hasValue("\\2")) { ?>', $content);
// < % if val1 == val2 % >
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) == "\\2") { ?>', $content);
$content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) == "\\2") { ?>', $content);
// < % if val1 != val2 % >
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *!= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) != "\\2") { ?>', $content);
$content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *!= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) != "\\2") { ?>', $content);
$content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) +%' . '>', '<? } else if(($test = $item->cachedCall("\\1")) && ((!is_object($test) && $test) || ($test && $test->exists()) )) { ?>', $content);
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? $test = $item->obj("\\1",null,true)->cachedCall("\\2"); if((!is_object($test) && $test) || ($test && $test->exists())) { ?>', $content);
$content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? } else if(($test = $item->obj("\\1",null,true)->cachedCall("\\2")) && ((!is_object($test) && $test) || ($test && $test->exists()) )) { ?>', $content);
$content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? $test = $item->obj("\\1",null,true)->obj("\\2",null,true)->cachedCall("\\3"); if((!is_object($test) && $test) || ($test && $test->exists())) { ?>', $content);
$content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? } else if(($test = $item->obj("\\1",null,true)->obj("\\2",null,true)->cachedCall("\\3")) && ((!is_object($test) && $test) || ($test && $test->exists()) )) { ?>', $content);
$content = ereg_replace('<' . '% +else +%' . '>', '<? } else { ?>', $content);
$content = ereg_replace('<' . '% +end_if +%' . '>', '<? } ?>', $content);
// i18n - get filename of currently parsed template
// CAUTION: No spaces allowed between arguments for all i18n calls!
ereg('.*[\/](.*)',$template,$path);
// i18n _t(...) - with entity only (no dots in namespace),
// meaning the current template filename will be added as a namespace.
// This applies only to "root" templates, not includes which should always have their namespace set already.
// See getTemplateContent() for more information.
$content = ereg_replace('<' . '% +_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '<?= _t(\''. $path[1] . '.\\2\\3\'\\4) ?>', $content);
// i18n _t(...)
$content = ereg_replace('<' . '% +_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '<?= _t(\'\\2\\3\'\\4) ?>', $content);
// i18n sprintf(_t(...),$argument) with entity only (no dots in namespace), meaning the current template filename will be added as a namespace
$content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\),\<\?= +([^\?]*) +\?\>) +%' . '>', '<?= sprintf(_t(\''. $path[1] . '.\\2\\3\'\\4),\\6) ?>', $content);
// i18n sprintf(_t(...),$argument)
$content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\),\<\?= +([^\?]*) +\?\>) +%' . '>', '<?= sprintf(_t(\'\\2\\3\'\\4),\\6) ?>', $content);
// </base> isnt valid html? !?
$content = ereg_replace('<' . '% +base_tag +%' . '>', '<?= SSViewer::get_base_tag($val); ?>', $content);
$content = ereg_replace('<' . '% +current_page +%' . '>', '<?= $_SERVER[SCRIPT_URL] ?>', $content);
// change < % require x() % > calls to corresponding Requirement::x() ones, including 0, 1 or 2 options
$content = preg_replace('/<% +require +([a-zA-Z]+)(?:\(([^),]+)\))? +%>/', '<? Requirements::\\1("\\2"); ?>', $content);
$content = preg_replace('/<% +require +([a-zA-Z]+)\(([^),]+), *([^),]+)\) +%>/', '<? Requirements::\\1("\\2", "\\3"); ?>', $content);
// Add include filename comments on dev sites
if(Director::isDev() && self::$source_file_comments) $replacementCode = 'return "<!-- include " . SSViewer::getTemplateFile($matches[1]) . " -->\n"
. "<?= SSViewer::parse_template(\\"" . $matches[1] . "\", \$item); ?>"
. "\n<!-- end include " . SSViewer::getTemplateFile($matches[1]) . " -->";';
else $replacementCode = 'return "<?= SSViewer::execute_template(\\"" . $matches[1] . "\", \$item); ?>";';
$content = preg_replace_callback('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', create_function(
'$matches', $replacementCode
), $content);
// legacy
$content = ereg_replace('<!-- +if +([A-Za-z0-9_]+) +-->', '<? if($item->cachedCall("\\1")) { ?>', $content);
$content = ereg_replace('<!-- +else +-->', '<? } else { ?>', $content);
$content = ereg_replace('<!-- +if_end +-->', '<? } ?>', $content);
// Fix link stuff
$content = ereg_replace('href *= *"#', 'href="<?= SSViewer::$options[\'rewriteHashlinks\'] ? Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "" ?>#', $content);
// Protect xml header
$content = ereg_replace('<\?xml([^>]+)\?' . '>', '<##xml\\1##>', $content);
// Turn PHP file into string definition
$content = str_replace('<?=',"\nSSVIEWER;\n\$val .= ", $content);
$content = str_replace('<?',"\nSSVIEWER;\n", $content);
$content = str_replace('?>',";\n \$val .= <<<SSVIEWER\n", $content);
$output = "<?php\n";
$output .= '$val .= <<<SSVIEWER' . "\n" . $content . "\nSSVIEWER;\n";
// Protect xml header @sam why is this run twice ?
$output = ereg_replace('<##xml([^>]+)##>', '<' . '?xml\\1?' . '>', $output);
return $output;
return SSTemplateParser::compileString($content, $template, Director::isDev() && self::$source_file_comments);
}
/**
@ -695,7 +689,7 @@ class SSViewer_FromString extends SSViewer {
echo "</pre>";
}
$itemStack = array();
$scope = new SSViewer_DataPresenter($item);
$val = "";
$valStack = array();
@ -708,256 +702,3 @@ class SSViewer_FromString extends SSViewer {
return $val;
}
}
/**
* Handle the parsing for cacheblock tags.
*
* Needs to be handled differently from the other tags, because cacheblock can take any number of arguments
*
* This shouldn't be used as an example of how to add functionality to SSViewer - the eventual plan is to re-write
* SSViewer using a proper parser (probably http://github.com/hafriedlander/php-peg), so that extra functionality
* can be added without relying on ad-hoc parsers like this.
*
* @package sapphire
* @subpackage view
*/
class SSViewer_PartialParser {
static $tag = '/< % [ \t]+ (cached|cacheblock|uncached|end_cached|end_cacheblock|end_uncached) [ \t]+ ([^%]+ [ \t]+)? % >/xS';
static $argument_splitter = '/^\s*
# The argument itself
(
(?P<conditional> if | unless ) | # The if or unless keybreak
(?P<property> (?P<identifier> \w+) \s* # A property lookup or a function call
( \( (?P<arguments> [^\)]*) \) )?
) |
(?P<sqstring> \' (\\\'|[^\'])+ \' ) | # A string surrounded by \'
(?P<dqstring> " (\\"|[^"])+ " ) # A string surrounded by "
)
# Some seperator after the argument
(
\s*(?P<comma>,)\s* | # A comma (maybe with whitespace before or after)
(?P<fullstop>\.) # A period (no whitespace before)
)?
/xS';
static function process($template, $content) {
$parser = new SSViewer_PartialParser($template, $content, 0);
$parser->parse();
return $parser->generate();
}
function __construct($template, $content, $offset) {
$this->template = $template;
$this->content = $content;
$this->offset = $offset;
$this->blocks = array();
}
function controlcheck($text) {
// NOP - hook for Cached_PartialParser
}
function parse() {
$current_tag_offset = 0;
while (preg_match(self::$tag, $this->content, $matches, PREG_OFFSET_CAPTURE, $this->offset)) {
$tag = $matches[1][0];
$startpos = $matches[0][1];
$endpos = $matches[0][1] + strlen($matches[0][0]);
switch($tag) {
case 'cached':
case 'uncached':
case 'cacheblock':
$pretext = substr($this->content, $this->offset, $startpos - $this->offset);
$this->controlcheck($pretext);
$this->blocks[] = $pretext;
if ($tag == 'cached' || $tag == 'cacheblock') {
list($keyparts, $conditional, $condition) = $this->parseargs(@$matches[2][0]);
$parser = new SSViewer_Cached_PartialParser($this->template, $this->content, $endpos, $keyparts, $conditional, $condition);
}
else {
$parser = new SSViewer_PartialParser($this->template, $this->content, $endpos);
}
$parser->parse();
$this->blocks[] = $parser;
$this->offset = $parser->offset;
break;
case 'end_cached':
case 'end_cacheblock':
case 'end_uncached':
$this->blocks[] = substr($this->content, $this->offset, $startpos - $this->offset);
$this->content = null;
$this->offset = $endpos;
return $this;
}
}
$this->blocks[] = substr($this->content, $this->offset);
$this->content = null;
}
function parseargs($string) {
preg_match_all(self::$argument_splitter, $string, $matches, PREG_SET_ORDER);
$parts = array();
$conditional = null; $condition = null;
$current = '$item->';
while (strlen($string) && preg_match(self::$argument_splitter, $string, $match)) {
$string = substr($string, strlen($match[0]));
// If this is a conditional keyword, break, and the next loop will grab the conditional
if (@$match['conditional']) {
$conditional = $match['conditional'];
continue;
}
// If it's a property lookup or a function call
if (@$match['property']) {
// Get the property
$what = $match['identifier'];
$args = array();
// Extract any arguments passed to the function call
if (@$match['arguments']) {
foreach (explode(',', $match['arguments']) as $arg) {
$args[] = is_numeric($arg) ? (string)$arg : '"'.$arg.'"';
}
}
$args = empty($args) ? 'null' : 'array('.implode(',',$args).')';
// If this fragment ended with '.', then there's another lookup coming, so return an obj for that lookup
if (@$match['fullstop']) {
$current .= "obj('$what', $args, true)->";
}
// Otherwise this is the end of the lookup chain, so add the resultant value to the key array and reset the key-get php fragement
else {
$accessor = $current . "XML_val('$what', $args, true)"; $current = '$item->';
// If we've hit a conditional already, this is the condition. Set it and be done.
if ($conditional) {
$condition = $accessor;
break;
}
// Otherwise we're another key component. Add it to array.
else $parts[] = $accessor;
}
}
// Else it's a quoted string of some kind
else if (@$match['sqstring']) $parts[] = $match['sqstring'];
else if (@$match['dqstring']) $parts[] = $match['dqstring'];
}
if ($conditional && !$condition) {
throw new Exception("You need to have a condition after the conditional $conditional in your cache block");
}
return array($parts, $conditional, $condition);
}
function generate() {
$res = array();
foreach ($this->blocks as $i => $block) {
if ($block instanceof SSViewer_PartialParser)
$res[] = $block->generate();
else {
$res[] = $block;
}
}
return implode('', $res);
}
}
/**
* @package sapphire
* @subpackage view
*/
class SSViewer_Cached_PartialParser extends SSViewer_PartialParser {
function __construct($template, $content, $offset, $keyparts, $conditional, $condition) {
$this->keyparts = $keyparts;
$this->conditional = $conditional;
$this->condition = $condition;
parent::__construct($template, $content, $offset);
}
function controlcheck($text) {
$ifs = preg_match_all('/<'.'% +if +/', $text, $matches);
$end_ifs = preg_match_all('/<'.'% +end_if +/', $text, $matches);
if ($ifs != $end_ifs) throw new Exception('You can\'t have cached or uncached blocks within condition structures');
$controls = preg_match_all('/<'.'% +control +/', $text, $matches);
$end_controls = preg_match_all('/<'.'% +end_control +/', $text, $matches);
if ($controls != $end_controls) throw new Exception('You can\'t have cached or uncached blocks within control structures');
}
function key() {
if (empty($this->keyparts)) return "''";
return 'sha1(' . implode(".'_'.", $this->keyparts) . ')';
}
function generate() {
$res = array();
$key = $this->key();
$condition = "";
switch ($this->conditional) {
case 'if':
$condition = "{$this->condition} && ";
break;
case 'unless':
$condition = "!({$this->condition}) && ";
break;
}
/* Output this set of blocks */
foreach ($this->blocks as $i => $block) {
if ($block instanceof SSViewer_PartialParser)
$res[] = $block->generate();
else {
// Include the template name and this cache block's current contents as a sha hash, so we get auto-seperation
// of cache blocks, and invalidation of the cache when the template changes
$partialkey = "'".sha1($this->template . $block)."_'.$key.'_$i'";
// Try to load from cache
$res[] = "<?\n".'if ('.$condition.' ($partial = $cache->load('.$partialkey.'))) $val .= $partial;'."\n";
// Cache miss - regenerate
$res[] = "else {\n";
$res[] = '$oldval = $val; $val = "";'."\n";
$res[] = "\n?>" . $block . "<?\n";
$res[] = $condition . ' $cache->save($val); $val = $oldval . $val ;'."\n";
$res[] = "}\n?>";
}
}
return implode('', $res);
}
}
function supressOutput() {
return "";
}
?>

View File

@ -186,6 +186,11 @@ class SSViewerCacheBlockTest extends SapphireTest {
$this->assertEquals($this->_runtemplate($template, array('Foo' => 2, 'Fooa' => 9, 'Foob' => 9, 'Bar' => 2, 'Bara' => 1)), ' 9 9 9 ');
}
function testNoErrorMessageForControlWithinCached() {
$this->_reset(true);
$this->_runtemplate('<% cached %><% control Foo %>$Bar<% end_control %><% end_cached %>');
}
/**
* @expectedException Exception
*/

View File

@ -218,10 +218,8 @@ after')
// Dot syntax
$this->assertEquals('ACD',
$this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D'));
// Broken currently
//$this->assertEquals('ACD',
// $this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D'));
$this->assertEquals('ACD',
$this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D'));
// Params
$this->assertEquals('ACD',
@ -229,6 +227,12 @@ after')
$this->assertEquals('ABD',
$this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D'));
// Negation
$this->assertEquals('AC',
$this->render('A<% if not IsSet %>B<% end_if %>C'));
$this->assertEquals('ABC',
$this->render('A<% if not NotSet %>B<% end_if %>C'));
// Or
$this->assertEquals('ABD',
$this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D'));
@ -236,12 +240,18 @@ after')
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D'));
$this->assertEquals('AD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D'));
$this->assertEquals('ACD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D'));
$this->assertEquals('AD',
$this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D'));
// Broken currently
//$this->assertEquals('ACD',
// $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D'));
//$this->assertEquals('AD',
// $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D'));
// Negated Or
$this->assertEquals('ACD',
$this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D'));
$this->assertEquals('ABD',
$this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D'));
$this->assertEquals('ABD',
$this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D'));
// And
$this->assertEquals('ABD',
@ -250,12 +260,10 @@ after')
$this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D'));
$this->assertEquals('AD',
$this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D'));
// Broken currently
//$this->assertEquals('ACD',
// $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D'));
//$this->assertEquals('AD',
// $this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D'));
$this->assertEquals('ACD',
$this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D'));
$this->assertEquals('AD',
$this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D'));
// Equality
$this->assertEquals('ABC',
@ -270,6 +278,10 @@ after')
// Else
$this->assertEquals('ADE',
$this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E'));
// Empty if with else
$this->assertEquals('ABC',
$this->render('A<% if NotSet %><% else %>B<% end_if %>C'));
}
function testBaseTagGeneration() {
@ -335,6 +347,137 @@ after')
$this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult);
}
function assertEqualIgnoringWhitespace($a, $b) {
$this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b));
}
/**
* Test $Up works when the scope $Up refers to was entered with a "with" block
*/
function testUpInWith() {
// Data to run the loop tests on - three levels deep
$data = new ArrayData(array(
'Name' => 'Top',
'Foo' => new ArrayData(array(
'Name' => 'Foo',
'Bar' => new ArrayData(array(
'Name' => 'Bar',
'Baz' => new ArrayData(array(
'Name' => 'Baz'
)),
'Qux' => new ArrayData(array(
'Name' => 'Qux'
))
))
))
));
// Basic functionality
$this->assertEquals('BarFoo',
$this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data));
// Two level with block, up refers to internally referenced Bar
$this->assertEquals('BarFoo',
$this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data));
// Stepping up & back down the scope tree
$this->assertEquals('BazBarQux',
$this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data));
// Using $Up in a with block
$this->assertEquals('BazBarQux',
$this->render('<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %><% end_with %>', $data));
// Stepping up & back down the scope tree with with blocks
$this->assertEquals('BazBarQuxBarBaz',
$this->render('<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>{$Name}<% end_with %>{$Name}<% end_with %>', $data));
// Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo
$this->assertEquals('Foo',
$this->render('<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %><% end_with %>', $data));
// Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo
$this->assertEquals('Foo',
$this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data));
}
/**
* Test $Up works when the scope $Up refers to was entered with a "loop" block
*/
function testUpInLoop(){
// Data to run the loop tests on - one sequence of three items, each with a subitem
$data = new ArrayData(array(
'Name' => 'Top',
'Foo' => new DataObjectSet(array(
new ArrayData(array(
'Name' => '1',
'Sub' => new ArrayData(array(
'Name' => 'Bar'
))
)),
new ArrayData(array(
'Name' => '2',
'Sub' => new ArrayData(array(
'Name' => 'Baz'
))
)),
new ArrayData(array(
'Name' => '3',
'Sub' => new ArrayData(array(
'Name' => 'Qux'
))
))
))
));
// Make sure inside a loop, $Up refers to the current item of the loop
$this->assertEqualIgnoringWhitespace(
'111 222 333',
$this->render(
'<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>',
$data
)
);
// Make sure inside a loop, looping over $Up uses a separate iterator,
// and doesn't interfere with the original iterator
$this->assertEqualIgnoringWhitespace(
'1Bar123Bar1 2Baz123Baz2 3Qux123Qux3',
$this->render(
'<% loop $Foo %>
$Name
<% with $Sub %>
$Name
<% loop $Up %>$Name<% end_loop %>
$Name
<% end_with %>
$Name
<% end_loop %>',
$data
)
);
// Make sure inside a loop, looping over $Up uses a separate iterator,
// and doesn't interfere with the original iterator or local lookups
$this->assertEqualIgnoringWhitespace(
'1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3',
$this->render(
'<% loop $Foo %>
$Name
<% with $Sub %>
{$Name}{$Up.Name}
<% loop $Up %>$Name<% end_loop %>
{$Up.Name}{$Name}
<% end_with %>
$Name
<% end_loop %>',
$data
)
);
}
}
/**
@ -345,6 +488,7 @@ class SSViewerTestFixture extends ViewableData {
function __construct($name = null) {
$this->name = $name;
parent::__construct();
}

9
thirdparty/php-peg/.piston.yml vendored Normal file
View File

@ -0,0 +1,9 @@
---
format: 1
handler:
commit: 2045d5fbfa3ed857a9eac3722e6f9ecc301593c6
branch: master
lock: false
repository_class: Piston::Git::Repository
repository_url: git://github.com/hafriedlander/php-peg.git
exported_to: 654df253d884db2cd397016de1a5105455ba1631

882
thirdparty/php-peg/Compiler.php vendored Normal file
View File

@ -0,0 +1,882 @@
<?php
/**
* PEG Generator - A PEG Parser for PHP
*
* @author Hamish Friedlander / SilverStripe
*
* See README.md for documentation
*
*/
require 'PHPBuilder.php' ;
class Flags {
function __construct( $parent = NULL ) {
$this->parent = $parent ;
$this->flags = array() ;
}
function __set( $k, $v ) {
$this->flags[$k] = $v ;
return $v ;
}
function __get( $k ) {
if ( isset( $this->flags[$k] ) ) return $this->flags[$k] ;
if ( isset( $this->parent ) ) return $this->parent->$k ;
return NULL ;
}
}
/**
* PHPWriter contains several code generation snippets that are used both by the Token and the Rule compiler
*/
class PHPWriter {
static $varid = 0 ;
function varid() {
return '_' . (self::$varid++) ;
}
function function_name( $str ) {
$str = preg_replace( '/-/', '_', $str ) ;
$str = preg_replace( '/\$/', 'DLR', $str ) ;
$str = preg_replace( '/\*/', 'STR', $str ) ;
$str = preg_replace( '/[^\w]+/', '', $str ) ;
return $str ;
}
function save($id) {
return PHPBuilder::build()
->l(
'$res'.$id.' = $result;',
'$pos'.$id.' = $this->pos;'
);
}
function restore( $id, $remove = FALSE ) {
$code = PHPBuilder::build()
->l(
'$result = $res'.$id.';',
'$this->pos = $pos'.$id.';'
);
if ( $remove ) $code->l(
'unset( $res'.$id.' );',
'unset( $pos'.$id.' );'
);
return $code ;
}
function match_fail_conditional( $on, $match = NULL, $fail = NULL ) {
return PHPBuilder::build()
->b( 'if (' . $on . ')',
$match,
'MATCH'
)
->b( 'else',
$fail,
'FAIL'
);
}
function match_fail_block( $code ) {
$id = $this->varid() ;
return PHPBuilder::build()
->l(
'$'.$id.' = NULL;'
)
->b( 'do',
$code->replace(array(
'MBREAK' => '$'.$id.' = TRUE; break;',
'FBREAK' => '$'.$id.' = FALSE; break;'
))
)
->l(
'while(0);'
)
->b( 'if( $'.$id.' === TRUE )', 'MATCH' )
->b( 'if( $'.$id.' === FALSE)', 'FAIL' )
;
}
}
/**
* A Token is any portion of a match rule. Tokens are responsible for generating the code to match against them.
*
* This base class provides the compile() function, which handles the token modifiers ( ? * + & ! )
*
* Each child class should provide the function match_code() which will generate the code to match against that specific token type.
* In that generated code they should include the lines MATCH or FAIL when a match or a decisive failure occurs. These will
* be overwritten when they are injected into parent Tokens or Rules. There is no requirement on where MATCH and FAIL can occur.
* They tokens are also responsible for storing and restoring state when nessecary to handle a non-decisive failure.
*
* @author hamish
*
*/
abstract class Token extends PHPWriter {
public $optional = FALSE ;
public $zero_or_more = FALSE ;
public $one_or_more = FALSE ;
public $positive_lookahead = FALSE ;
public $negative_lookahead = FALSE ;
public $silent = FALSE ;
public $tag = FALSE ;
public $type ;
public $value ;
function __construct( $type, $value = NULL ) {
$this->type = $type ;
$this->value = $value ;
}
// abstract protected function match_code() ;
function compile() {
$code = $this->match_code() ;
$id = $this->varid() ;
if ( $this->optional ) {
$code = PHPBuilder::build()
->l(
$this->save($id),
$code->replace( array( 'FAIL' => $this->restore($id,true) ))
);
}
if ( $this->zero_or_more ) {
$code = PHPBuilder::build()
->b( 'while (true)',
$this->save($id),
$code->replace( array(
'MATCH' => NULL,
'FAIL' =>
$this->restore($id,true)
->l( 'break;' )
))
)
->l(
'MATCH'
);
}
if ( $this->one_or_more ) {
$code = PHPBuilder::build()
->l(
'$count = 0;'
)
->b( 'while (true)',
$this->save($id),
$code->replace( array(
'MATCH' => NULL,
'FAIL' =>
$this->restore($id,true)
->l( 'break;' )
)),
'$count += 1;'
)
->b( 'if ($count > 0)', 'MATCH' )
->b( 'else', 'FAIL' );
}
if ( $this->positive_lookahead ) {
$code = PHPBuilder::build()
->l(
$this->save($id),
$code->replace( array(
'MATCH' =>
$this->restore($id)
->l( 'MATCH' ),
'FAIL' =>
$this->restore($id)
->l( 'FAIL' )
)));
}
if ( $this->negative_lookahead ) {
$code = PHPBuilder::build()
->l(
$this->save($id),
$code->replace( array(
'MATCH' =>
$this->restore($id)
->l( 'FAIL' ),
'FAIL' =>
$this->restore($id)
->l( 'MATCH' )
)));
}
if ( $this->tag && !($this instanceof TokenRecurse ) ) {
$code = PHPBuilder::build()
->l(
'$stack[] = $result; $result = $this->construct( $matchrule, "'.$this->tag.'" ); ',
$code->replace(array(
'MATCH' => PHPBuilder::build()
->l(
'$subres = $result; $result = array_pop($stack);',
'$this->store( $result, $subres, \''.$this->tag.'\' );',
'MATCH'
),
'FAIL' => PHPBuilder::build()
->l(
'$result = array_pop($stack);',
'FAIL'
)
)));
}
return $code ;
}
}
abstract class TokenTerminal extends Token {
function set_text( $text ) {
return $this->silent ? NULL : '$result["text"] .= ' . $text . ';';
}
protected function match_code( $value ) {
return $this->match_fail_conditional( '( $subres = $this->'.$this->type.'( '.$value.' ) ) !== FALSE',
$this->set_text('$subres')
);
}
}
abstract class TokenExpressionable extends TokenTerminal {
static $expression_rx = '/ \$(\w+) | { \$(\w+) } /x';
function contains_expression(){
return preg_match(self::$expression_rx, $this->value);
}
function expression_replace($matches) {
return '\'.$this->expression($result, $stack, \'' . (!empty($matches[1]) ? $matches[1] : $matches[2]) . "').'";
}
function match_code( $value ) {
$value = preg_replace_callback(self::$expression_rx, array($this, 'expression_replace'), $value);
return parent::match_code($value);
}
}
class TokenLiteral extends TokenExpressionable {
function __construct( $value ) {
parent::__construct( 'literal', "'" . substr($value,1,-1) . "'" );
}
function match_code() {
// We inline single-character matches for speed
if ( !$this->contains_expression() && strlen( eval( 'return '. $this->value . ';' ) ) == 1 ) {
return $this->match_fail_conditional( 'substr($this->string,$this->pos,1) == '.$this->value,
PHPBuilder::build()->l(
'$this->pos += 1;',
$this->set_text( $this->value )
)
);
}
return parent::match_code($this->value);
}
}
class TokenRegex extends TokenExpressionable {
static function escape( $rx ) {
$rx = str_replace( "'", "\\'", $rx ) ;
$rx = str_replace( '\\\\', '\\\\\\\\', $rx ) ;
return $rx ;
}
function __construct( $value ) {
parent::__construct('rx', self::escape($value));
}
function match_code() {
return parent::match_code("'{$this->value}'");
}
}
class TokenWhitespace extends TokenTerminal {
function __construct( $optional ) {
parent::__construct( 'whitespace', $optional ) ;
}
/* Call recursion indirectly */
function match_code() {
$code = parent::match_code( '' ) ;
return $this->value ? $code->replace( array( 'FAIL' => NULL )) : $code ;
}
}
class TokenRecurse extends Token {
function __construct( $value ) {
parent::__construct( 'recurse', $value ) ;
}
function match_function() {
return "'".$this->function_name($this->value)."'";
}
function match_code() {
$function = $this->match_function() ;
$storetag = $this->function_name( $this->tag ? $this->tag : $this->match_function() ) ;
if ( ParserCompiler::$debug ) {
$debug_header = PHPBuilder::build()
->l(
'$indent = str_repeat( " ", $this->depth );',
'$this->depth += 2;',
'$sub = ( strlen( $this->string ) - $this->pos > 20 ) ? ( substr( $this->string, $this->pos, 20 ) . "..." ) : substr( $this->string, $this->pos );',
'$sub = preg_replace( \'/(\r|\n)+/\', " {NL} ", $sub );',
'print( $indent."Matching against $matcher (".$sub.")\n" );'
);
$debug_match = PHPBuilder::build()
->l(
'print( $indent."MATCH\n" );',
'$this->depth -= 2;'
);
$debug_fail = PHPBuilder::build()
->l(
'print( $indent."FAIL\n" );',
'$this->depth -= 2;'
);
}
else {
$debug_header = $debug_match = $debug_fail = NULL ;
}
return PHPBuilder::build()->l(
'$matcher = \'match_\'.'.$function.'; $key = $matcher; $pos = $this->pos;',
$debug_header,
'$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );',
$this->match_fail_conditional( '$subres !== FALSE',
PHPBuilder::build()->l(
$debug_match,
$this->tag === FALSE ?
'$this->store( $result, $subres );' :
'$this->store( $result, $subres, "'.$storetag.'" );'
),
PHPBuilder::build()->l(
$debug_fail
)
));
}
}
class TokenExpressionedRecurse extends TokenRecurse {
function match_function() {
return '$this->expression($result, $stack, \''.$this->value.'\')';
}
}
class TokenSequence extends Token {
function __construct( $value ) {
parent::__construct( 'sequence', $value ) ;
}
function match_code() {
$code = PHPBuilder::build() ;
foreach( $this->value as $token ) {
$code->l(
$token->compile()->replace(array(
'MATCH' => NULL,
'FAIL' => 'FBREAK'
))
);
}
$code->l( 'MBREAK' );
return $this->match_fail_block( $code ) ;
}
}
class TokenOption extends Token {
function __construct( $opt1, $opt2 ) {
parent::__construct( 'option', array( $opt1, $opt2 ) ) ;
}
function match_code() {
$id = $this->varid() ;
$code = PHPBuilder::build()
->l(
$this->save($id)
) ;
foreach ( $this->value as $opt ) {
$code->l(
$opt->compile()->replace(array(
'MATCH' => 'MBREAK',
'FAIL' => NULL
)),
$this->restore($id)
);
}
$code->l( 'FBREAK' ) ;
return $this->match_fail_block( $code ) ;
}
}
/**
* Handles storing of information for an expression that applys to the <i>next</i> token, and deletion of that
* information after applying
*
* @author Hamish Friedlander
*/
class Pending {
function __construct() {
$this->what = NULL ;
}
function set( $what, $val = TRUE ) {
$this->what = $what ;
$this->val = $val ;
}
function apply_if_present( $on ) {
if ( $this->what !== NULL ) {
$what = $this->what ;
$on->$what = $this->val ;
$this->what = NULL ;
}
}
}
/**
* Rule parsing and code generation
*
* A rule is the basic unit of a PEG. This parses one rule, and generates a function that will match on a string
*
* @author Hamish Friedlander
*/
class Rule extends PHPWriter {
static $rule_rx = '@
(?<name> \w+) # The name of the rule
( \s+ extends \s+ (?<extends>\w+) )? # The extends word
( \s* \( (?<arguments>.*) \) )? # Any variable setters
(
\s*(?<matchmark>:) | # Marks the matching rule start
\s*(?<replacemark>;) | # Marks the replacing rule start
\s*$
)
(?<rule>[\s\S]*)
@x';
static $argument_rx = '@
( [^=]+ ) # Name
= # Seperator
( [^=,]+ ) # Variable
(,|$)
@x';
static $replacement_rx = '@
( ([^=]|=[^>])+ ) # What to replace
=> # The replacement mark
( [^,]+ ) # What to replace it with
(,|$)
@x';
static $function_rx = '@^\s+function\s+([^\s(]+)\s*(.*)@' ;
protected $parser;
protected $lines;
public $name;
public $extends;
public $mode;
public $rule;
function __construct($parser, $lines) {
$this->parser = $parser;
$this->lines = $lines;
// Find the first line (if any) that's an attached function definition. Can skip first line (unless this block is malformed)
for ($i = 1; $i < count($lines); $i++) {
if (preg_match(self::$function_rx, $lines[$i])) break;
}
// Then split into the two parts
$spec = array_slice($lines, 0, $i);
$funcs = array_slice($lines, $i);
// Parse out the spec
$spec = implode("\n", $spec);
if (!preg_match(self::$rule_rx, $spec, $specmatch)) user_error('Malformed rule spec ' . $spec, E_USER_ERROR);
$this->name = $specmatch['name'];
if ($specmatch['extends']) {
$this->extends = $this->parser->rules[$specmatch['extends']];
if (!$this->extends) user_error('Extended rule '.$specmatch['extends'].' is not defined before being extended', E_USER_ERROR);
}
$this->arguments = array();
if ($specmatch['arguments']) {
preg_match_all(self::$argument_rx, $specmatch['arguments'], $arguments, PREG_SET_ORDER);
foreach ($arguments as $argument){
$this->arguments[trim($argument[1])] = trim($argument[2]);
}
}
$this->mode = $specmatch['matchmark'] ? 'rule' : 'replace';
if ($this->mode == 'rule') {
$this->rule = $specmatch['rule'];
$this->parse_rule() ;
}
else {
if (!$this->extends) user_error('Replace matcher, but not on an extends rule', E_USER_ERROR);
$this->replacements = array();
preg_match_all(self::$replacement_rx, $specmatch['rule'], $replacements, PREG_SET_ORDER);
$rule = $this->extends->rule;
foreach ($replacements as $replacement) {
$search = trim($replacement[1]);
$replace = trim($replacement[3]); if ($replace == "''" || $replace == '""') $replace = "";
$rule = str_replace($search, ' '.$replace.' ', $rule);
}
$this->rule = $rule;
$this->parse_rule() ;
}
// Parse out the functions
$this->functions = array() ;
$active_function = NULL ;
foreach( $funcs as $line ) {
/* Handle function definitions */
if ( preg_match( self::$function_rx, $line, $func_match, 0 ) ) {
$active_function = $func_match[1];
$this->functions[$active_function] = $func_match[2] . PHP_EOL;
}
else $this->functions[$active_function] .= $line . PHP_EOL ;
}
}
/* Manual parsing, because we can't bootstrap ourselves yet */
function parse_rule() {
$rule = trim( $this->rule ) ;
/* If this is a regex end-token, just mark it and return */
if ( substr( $rule, 0, 1 ) == '/' ) {
$this->parsed = new TokenRegex( $rule ) ;
}
else {
$tokens = array() ;
$this->tokenize( $rule, $tokens ) ;
$this->parsed = ( count( $tokens ) == 1 ? array_pop( $tokens ) : new TokenSequence( $tokens ) ) ;
}
}
static $rx_rx = '{^/(
((\\\\\\\\)*\\\\/) # Escaped \/, making sure to catch all the \\ first, so that we dont think \\/ is an escaped /
|
[^/] # Anything except /
)*/}xu' ;
function tokenize( $str, &$tokens, $o = 0 ) {
$pending = new Pending() ;
while ( $o < strlen( $str ) ) {
$sub = substr( $str, $o ) ;
/* Absorb white-space */
if ( preg_match( '/^\s+/', $sub, $match ) ) {
$o += strlen( $match[0] ) ;
}
/* Handle expression labels */
elseif ( preg_match( '/^(\w*):/', $sub, $match ) ) {
$pending->set( 'tag', isset( $match[1] ) ? $match[1] : '' ) ;
$o += strlen( $match[0] ) ;
}
/* Handle descent token */
elseif ( preg_match( '/^[\w-]+/', $sub, $match ) ) {
$tokens[] = $t = new TokenRecurse( $match[0] ) ; $pending->apply_if_present( $t ) ;
$o += strlen( $match[0] ) ;
}
/* Handle " quoted literals */
elseif ( preg_match( '/^"[^"]*"/', $sub, $match ) ) {
$tokens[] = $t = new TokenLiteral( $match[0] ) ; $pending->apply_if_present( $t ) ;
$o += strlen( $match[0] ) ;
}
/* Handle ' quoted literals */
elseif ( preg_match( "/^'[^']*'/", $sub, $match ) ) {
$tokens[] = $t = new TokenLiteral( $match[0] ) ; $pending->apply_if_present( $t ) ;
$o += strlen( $match[0] ) ;
}
/* Handle regexs */
elseif ( preg_match( self::$rx_rx, $sub, $match ) ) {
$tokens[] = $t = new TokenRegex( $match[0] ) ; $pending->apply_if_present( $t ) ;
$o += strlen( $match[0] ) ;
}
/* Handle $ call literals */
elseif ( preg_match( '/^\$(\w+)/', $sub, $match ) ) {
$tokens[] = $t = new TokenExpressionedRecurse( $match[1] ) ; $pending->apply_if_present( $t ) ;
$o += strlen( $match[0] ) ;
}
/* Handle flags */
elseif ( preg_match( '/^\@(\w+)/', $sub, $match ) ) {
$l = count( $tokens ) - 1 ;
$o += strlen( $match[0] ) ;
user_error( "TODO: Flags not currently supported", E_USER_WARNING ) ;
}
/* Handle control tokens */
else {
$c = substr( $sub, 0, 1 ) ;
$l = count( $tokens ) - 1 ;
$o += 1 ;
switch( $c ) {
case '?':
$tokens[$l]->optional = TRUE ;
break ;
case '*':
$tokens[$l]->zero_or_more = TRUE ;
break ;
case '+':
$tokens[$l]->one_or_more = TRUE ;
break ;
case '&':
$pending->set( 'positive_lookahead' ) ;
break ;
case '!':
$pending->set( 'negative_lookahead' ) ;
break ;
case '.':
$pending->set( 'silent' );
break;
case '[':
case ']':
$tokens[] = new TokenWhitespace( FALSE ) ;
break ;
case '<':
case '>':
$tokens[] = new TokenWhitespace( TRUE ) ;
break ;
case '(':
$subtokens = array() ;
$o = $this->tokenize( $str, $subtokens, $o ) ;
$tokens[] = $t = new TokenSequence( $subtokens ) ; $pending->apply_if_present( $t ) ;
break ;
case ')':
return $o ;
case '|':
$option1 = $tokens ;
$option2 = array() ;
$o = $this->tokenize( $str, $option2, $o ) ;
$option1 = (count($option1) == 1) ? $option1[0] : new TokenSequence( $option1 );
$option2 = (count($option2) == 1) ? $option2[0] : new TokenSequence( $option2 );
$pending->apply_if_present( $option2 ) ;
$tokens = array( new TokenOption( $option1, $option2 ) ) ;
return $o ;
default:
user_error( "Can't parser $c - attempting to skip", E_USER_WARNING ) ;
}
}
}
return $o ;
}
/**
* Generate the PHP code for a function to match against a string for this rule
*/
function compile($indent) {
$function_name = $this->function_name( $this->name ) ;
// Build the typestack
$typestack = array(); $class=$this;
do {
$typestack[] = $this->function_name($class->name);
}
while($class = $class->extends);
$typestack = "array('" . implode("','", $typestack) . "')";
// Build an array of additional arguments to add to result node (if any)
if (empty($this->arguments)) {
$arguments = 'null';
}
else {
$arguments = "array(";
foreach ($this->arguments as $k=>$v) { $arguments .= "'$k' => '$v'"; }
$arguments .= ")";
}
$match = PHPBuilder::build() ;
$match->l("protected \$match_{$function_name}_typestack = $typestack;");
$match->b( "function match_{$function_name} (\$stack = array())",
'$matchrule = "'.$function_name.'"; $result = $this->construct($matchrule, $matchrule, '.$arguments.');',
$this->parsed->compile()->replace(array(
'MATCH' => 'return $this->finalise($result);',
'FAIL' => 'return FALSE;'
))
);
$functions = array() ;
foreach( $this->functions as $name => $function ) {
$function_name = $this->function_name( preg_match( '/^_/', $name ) ? $this->name.$name : $this->name.'_'.$name ) ;
$functions[] = implode( PHP_EOL, array(
'function ' . $function_name . ' ' . $function
));
}
// print_r( $match ) ; return '' ;
return $match->render(NULL, $indent) . PHP_EOL . PHP_EOL . implode( PHP_EOL, $functions ) ;
}
}
class RuleSet {
public $rules = array();
function addRule($indent, $lines, &$out) {
$rule = new Rule($this, $lines) ;
$this->rules[$rule->name] = $rule;
$out[] = $indent . '/* ' . $rule->name . ':' . $rule->rule . ' */' . PHP_EOL ;
$out[] = $rule->compile($indent) ;
$out[] = PHP_EOL ;
}
function compile($indent, $rulestr) {
$indentrx = '@^'.preg_quote($indent).'@';
$out = array();
$block = array();
foreach (preg_split('/\r\n|\r|\n/', $rulestr) as $line) {
// Ignore blank lines
if (!trim($line)) continue;
// Ignore comments
if (preg_match('/^[\x20|\t]+#/', $line)) continue;
// Strip off indent
if (!empty($indent)) {
if (strpos($line, $indent) === 0) $line = substr($line, strlen($indent));
else user_error('Non-blank line with inconsistent index in parser block', E_USER_ERROR);
}
// Any indented line, add to current set of lines
if (preg_match('/^\x20|\t/', $line)) $block[] = $line;
// Any non-indented line marks a new block. Add a rule for the current block, then start a new block
else {
if (count($block)) $this->addRule($indent, $block, $out);
$block = array($line);
}
}
// Any unfinished block add a rule for
if (count($block)) $this->addRule($indent, $block, $out);
// And return the compiled version
return implode( '', $out ) ;
}
}
class ParserCompiler {
static $parsers = array();
static $debug = false;
static $currentClass = null;
static function create_parser( $match ) {
/* We allow indenting of the whole rule block, but only to the level of the comment start's indent */
$indent = $match[1];
/* Get the parser name for this block */
if ($class = trim($match[2])) self::$currentClass = $class;
elseif (self::$currentClass) $class = self::$currentClass;
else $class = self::$currentClass = 'Anonymous Parser';
/* Check for pragmas */
if (strpos($class, '!') === 0) {
switch ($class) {
case '!silent':
// NOP - dont output
return '';
case '!insert_autogen_warning':
return $indent . implode(PHP_EOL.$indent, array(
'/*',
'WARNING: This file has been machine generated. Do not edit it, or your changes will be overwritten next time it is compiled.',
'*/'
)) . PHP_EOL;
case '!debug':
self::$debug = true;
return '';
}
throw new Exception("Unknown pragma $class encountered when compiling parser");
}
if (!isset(self::$parsers[$class])) self::$parsers[$class] = new RuleSet();
return self::$parsers[$class]->compile($indent, $match[3]);
}
static function compile( $string ) {
static $rx = '@
^([\x20\t]*)/\*!\* (?:[\x20\t]*(!?\w*))? # Start with some indent, a comment with the special marker, then an optional name
((?:[^*]|\*[^/])*) # Any amount of "a character that isnt a star, or a star not followed by a /
\*/ # The comment end
@mx';
return preg_replace_callback( $rx, array( 'ParserCompiler', 'create_parser' ), $string ) ;
}
static function cli( $args ) {
if ( count( $args ) == 1 ) {
print "Parser Compiler: A compiler for PEG parsers in PHP \n" ;
print "(C) 2009 SilverStripe. See COPYING for redistribution rights. \n" ;
print "\n" ;
print "Usage: {$args[0]} infile [ outfile ]\n" ;
print "\n" ;
}
else {
$fname = ( $args[1] == '-' ? 'php://stdin' : $args[1] ) ;
$string = file_get_contents( $fname ) ;
$string = self::compile( $string ) ;
if ( !empty( $args[2] ) && $args[2] != '-' ) {
file_put_contents( $args[2], $string ) ;
}
else {
print $string ;
}
}
}
}

10
thirdparty/php-peg/LICENSE vendored Normal file
View File

@ -0,0 +1,10 @@
Copyright (C) 2009 Hamish Friedlander (hamish@silverstripe.com) and SilverStripe Limited (www.silverstripe.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Hamish Friedlander nor SilverStripe nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

127
thirdparty/php-peg/PHPBuilder.php vendored Normal file
View File

@ -0,0 +1,127 @@
<?php
class PHPBuilder {
static function build () {
return new PHPBuilder() ;
}
function __construct() {
$this->lines = array() ;
}
function l() {
foreach ( func_get_args() as $lines ) {
if ( !$lines ) continue ;
if ( is_string( $lines ) ) $lines = preg_split( '/\r\n|\r|\n/', $lines ) ;
if ( !$lines ) continue ;
if ( $lines instanceof PHPBuilder ) $lines = $lines->lines ;
else $lines = array_map( 'ltrim', $lines ) ;
if ( !$lines ) continue ;
$this->lines = array_merge( $this->lines, $lines ) ;
}
return $this ;
}
function b() {
$args = func_get_args() ;
$entry = array_shift( $args ) ;
$block = new PHPBuilder() ;
call_user_func_array( array( $block, 'l' ), $args ) ;
$this->lines[] = array( $entry, $block->lines ) ;
return $this ;
}
function replace( $replacements, &$array = NULL ) {
if ( $array === NULL ) {
unset( $array ) ;
$array =& $this->lines ;
}
$i = 0 ;
while ( $i < count( $array ) ) {
/* Recurse into blocks */
if ( is_array( $array[$i] ) ) {
$this->replace( $replacements, $array[$i][1] ) ;
if ( count( $array[$i][1] ) == 0 ) {
$nextelse = isset( $array[$i+1] ) && is_array( $array[$i+1] ) && preg_match( '/^\s*else\s*$/i', $array[$i+1][0] ) ;
$delete = preg_match( '/^\s*else\s*$/i', $array[$i][0] ) ;
$delete = $delete || ( preg_match( '/^\s*if\s*\(/i', $array[$i][0] ) && !$nextelse ) ;
if ( $delete ) {
// Is this always safe? Not if the expression has side-effects.
// print "/* REMOVING EMPTY BLOCK: " . $array[$i][0] . "*/\n" ;
array_splice( $array, $i, 1 ) ;
continue ;
}
}
}
/* Handle replacing lines with NULL to remove, or string, array of strings or PHPBuilder to replace */
else {
if ( array_key_exists( $array[$i], $replacements ) ) {
$rep = $replacements[$array[$i]] ;
if ( $rep === NULL ) {
array_splice( $array, $i, 1 ) ;
continue ;
}
if ( is_string( $rep ) ) {
$array[$i] = $rep ;
$i++ ;
continue ;
}
if ( $rep instanceof PHPBuilder ) $rep = $rep->lines ;
if ( is_array( $rep ) ) {
array_splice( $array, $i, 1, $rep ) ; $i += count( $rep ) + 1 ;
continue ;
}
throw 'Unknown type passed to PHPBuilder#replace' ;
}
}
$i++ ;
}
return $this ;
}
function render( $array = NULL, $indent = "" ) {
if ( $array === NULL ) $array = $this->lines ;
$out = array() ;
foreach( $array as $line ) {
if ( is_array( $line ) ) {
list( $entry, $block ) = $line ;
$str = $this->render( $block, $indent . "\t" ) ;
if ( strlen( $str ) < 40 ) {
$out[] = $indent . $entry . ' { ' . ltrim( $str ) . ' }' ;
}
else {
$out[] = $indent . $entry . ' {' ;
$out[] = $str ;
$out[] = $indent . '}' ;
}
}
else {
$out[] = $indent . $line ;
}
}
return implode( PHP_EOL, $out ) ;
}
}

292
thirdparty/php-peg/Parser.php vendored Normal file
View File

@ -0,0 +1,292 @@
<?php
/**
* We cache the last regex result. This is a low-cost optimization, because we have to do an un-anchored match + check match position anyway
* (alternative is to do an anchored match on a string cut with substr, but that is very slow for long strings). We then don't need to recheck
* for any position between current position and eventual match position - result will be the same
*
* Of course, the next regex might be outside that bracket - after the bracket if other matches have progressed beyond the match position, or before
* the bracket if a failed match + restore has moved the current position backwards - so we have to check that too.
*/
class ParserRegexp {
function __construct( $parser, $rx ) {
$this->parser = $parser ;
$this->rx = $rx . 'Sx' ;
$this->matches = NULL ;
$this->match_pos = NULL ; // NULL is no-match-to-end-of-string, unless check_pos also == NULL, in which case means undefined
$this->check_pos = NULL ;
}
function match() {
$current_pos = $this->parser->pos ;
$dirty = $this->check_pos === NULL || $this->check_pos > $current_pos || ( $this->match_pos !== NULL && $this->match_pos < $current_pos ) ;
if ( $dirty ) {
$this->check_pos = $current_pos ;
$matched = preg_match( $this->rx, $this->parser->string, $this->matches, PREG_OFFSET_CAPTURE, $this->check_pos) ;
if ( $matched ) $this->match_pos = $this->matches[0][1] ; else $this->match_pos = NULL ;
}
if ( $this->match_pos === $current_pos ) {
$this->parser->pos += strlen( $this->matches[0][0] );
return $this->matches[0][0] ;
}
return FALSE ;
}
}
/**
* Parser base class
* - handles current position in string
* - handles matching that position against literal or rx
* - some abstraction of code that would otherwise be repeated many times in a compiled grammer, mostly related to calling user functions
* for result construction and building
*/
class Parser {
function __construct( $string ) {
$this->string = $string ;
$this->pos = 0 ;
$this->depth = 0 ;
$this->regexps = array() ;
}
function whitespace() {
$matched = preg_match( '/[ \t]+/', $this->string, $matches, PREG_OFFSET_CAPTURE, $this->pos ) ;
if ( $matched && $matches[0][1] == $this->pos ) {
$this->pos += strlen( $matches[0][0] );
return ' ' ;
}
return FALSE ;
}
function literal( $token ) {
/* Debugging: * / print( "Looking for token '$token' @ '" . substr( $this->string, $this->pos ) . "'\n" ) ; /* */
$toklen = strlen( $token ) ;
$substr = substr( $this->string, $this->pos, $toklen ) ;
if ( $substr == $token ) {
$this->pos += $toklen ;
return $token ;
}
return FALSE ;
}
function rx( $rx ) {
if ( !isset( $this->regexps[$rx] ) ) $this->regexps[$rx] = new ParserRegexp( $this, $rx ) ;
return $this->regexps[$rx]->match() ;
}
function expression( $result, $stack, $value ) {
$stack[] = $result; $rv = false;
/* Search backwards through the sub-expression stacks */
for ( $i = count($stack) - 1 ; $i >= 0 ; $i-- ) {
$node = $stack[$i];
if ( isset($node[$value]) ) { $rv = $node[$value]; break; }
foreach ($this->typestack($node['_matchrule']) as $type) {
$callback = array($this, "{$type}_DLR{$value}");
if ( is_callable( $callback ) ) { $rv = call_user_func( $callback ) ; if ($rv !== FALSE) break; }
}
}
if ($rv === false) $rv = @$this->$value;
if ($rv === false) $rv = @$this->$value();
return is_array($rv) ? $rv['text'] : ($rv ? $rv : '');
}
function packhas( $key, $pos ) {
return false ;
}
function packread( $key, $pos ) {
throw 'PackRead after PackHas=>false in Parser.php' ;
}
function packwrite( $key, $pos, $res ) {
return $res ;
}
function typestack( $name ) {
$prop = "match_{$name}_typestack";
return $this->$prop;
}
function construct( $matchrule, $name, $arguments = null ) {
$result = array( '_matchrule' => $matchrule, 'name' => $name, 'text' => '' );
if ($arguments) $result = array_merge($result, $arguments) ;
foreach ($this->typestack($matchrule) as $type) {
$callback = array( $this, "{$type}__construct" ) ;
if ( is_callable( $callback ) ) {
call_user_func_array( $callback, array( &$result ) ) ;
break;
}
}
return $result ;
}
function finalise( &$result ) {
foreach ($this->typestack($result['_matchrule']) as $type) {
$callback = array( $this, "{$type}__finalise" ) ;
if ( is_callable( $callback ) ) {
call_user_func_array( $callback, array( &$result ) ) ;
break;
}
}
return $result ;
}
function store ( &$result, $subres, $storetag = NULL ) {
$result['text'] .= $subres['text'] ;
$storecalled = false;
foreach ($this->typestack($result['_matchrule']) as $type) {
$callback = array( $this, $storetag ? "{$type}_{$storetag}" : "{$type}_{$subres['name']}" ) ;
if ( is_callable( $callback ) ) {
call_user_func_array( $callback, array( &$result, $subres ) ) ;
$storecalled = true; break;
}
$globalcb = array( $this, "{$type}_STR" ) ;
if ( is_callable( $globalcb ) ) {
call_user_func_array( $globalcb, array( &$result, $subres ) ) ;
$storecalled = true; break;
}
}
if ( $storetag && !$storecalled ) {
if ( !isset( $result[$storetag] ) ) $result[$storetag] = $subres ;
else {
if ( isset( $result[$storetag]['text'] ) ) $result[$storetag] = array( $result[$storetag] ) ;
$result[$storetag][] = $subres ;
}
}
}
}
/**
* By inheriting from Packrat instead of Parser, the parser will run in linear time (instead of exponential like
* Parser), but will require a lot more memory, since every match-attempt at every position is memorised.
*
* We now use a string as a byte-array to store position information rather than a straight array for memory reasons. This
* means there is a (roughly) 8MB limit on the size of the string we can parse
*
* @author Hamish Friedlander
*/
class Packrat extends Parser {
function __construct( $string ) {
parent::__construct( $string ) ;
$max = unpack( 'N', "\x00\xFD\xFF\xFF" ) ;
if ( strlen( $string ) > $max[1] ) user_error( 'Attempting to parse string longer than Packrat Parser can handle', E_USER_ERROR ) ;
$this->packstatebase = str_repeat( "\xFF", strlen( $string )*3 ) ;
$this->packstate = array() ;
$this->packres = array() ;
}
function packhas( $key, $pos ) {
$pos *= 3 ;
return isset( $this->packstate[$key] ) && $this->packstate[$key][$pos] != "\xFF" ;
}
function packread( $key, $pos ) {
$pos *= 3 ;
if ( $this->packstate[$key][$pos] == "\xFE" ) return FALSE ;
$this->pos = ord($this->packstate[$key][$pos]) << 16 | ord($this->packstate[$key][$pos+1]) << 8 | ord($this->packstate[$key][$pos+2]) ;
return $this->packres["$key:$pos"] ;
}
function packwrite( $key, $pos, $res ) {
if ( !isset( $this->packstate[$key] ) ) $this->packstate[$key] = $this->packstatebase ;
$pos *= 3 ;
if ( $res !== FALSE ) {
$i = pack( 'N', $this->pos ) ;
$this->packstate[$key][$pos] = $i[1] ;
$this->packstate[$key][$pos+1] = $i[2] ;
$this->packstate[$key][$pos+2] = $i[3] ;
$this->packres["$key:$pos"] = $res ;
}
else {
$this->packstate[$key][$pos] = "\xFE" ;
}
return $res ;
}
}
/**
* FalseOnlyPackrat only remembers which results where false. Experimental.
*
* @author Hamish Friedlander
*/
class FalseOnlyPackrat extends Parser {
function __construct( $string ) {
parent::__construct( $string ) ;
$this->packstatebase = str_repeat( '.', strlen( $string ) ) ;
$this->packstate = array() ;
}
function packhas( $key, $pos ) {
return isset( $this->packstate[$key] ) && $this->packstate[$key][$pos] == 'F' ;
}
function packread( $key, $pos ) {
return FALSE ;
}
function packwrite( $key, $pos, $res ) {
if ( !isset( $this->packstate[$key] ) ) $this->packstate[$key] = $this->packstatebase ;
if ( $res === FALSE ) {
$this->packstate[$key][$pos] = 'F' ;
}
return $res ;
}
}
/**
* Conservative Packrat will only memo-ize a result on the second hit, making it more memory-lean than Packrat,
* but less likely to go exponential that Parser. Because the store logic is much more complicated this is a net
* loss over Parser for many simple grammars.
*
* @author Hamish Friedlander
*/
class ConservativePackrat extends Parser {
function packhas( $key ) {
return isset( $this->packres[$key] ) && $this->packres[$key] !== NULL ;
}
function packread( $key ) {
$this->pos = $this->packpos[$key];
return $this->packres[$key] ;
}
function packwrite( $key, $res ) {
if ( isset( $this->packres[$key] ) ) {
$this->packres[$key] = $res ;
$this->packpos[$key] = $this->pos ;
}
else {
$this->packres[$key] = NULL ;
}
return $res ;
}
}

329
thirdparty/php-peg/README.md vendored Normal file
View File

@ -0,0 +1,329 @@
# PHP PEG - A PEG compiler for parsing text in PHP
This is a Paring Expression Grammar compiler for PHP. PEG parsers are an alternative to other CFG grammars that includes both tokenization
and lexing in a single top down grammar. For a basic overview of the subject, see http://en.wikipedia.org/wiki/Parsing_expression_grammar
## Quick start
- Write a parser. A parser is a PHP class with a grammar contained within it in a special syntax. The filetype is .peg.inc. See the examples directory.
- Compile the parser. php ./cli.php ExampleParser.peg.inc > ExampleParser.php
- Use the parser (you can also include code to do this in the input parser - again see the examples directory):
<pre><code>
$x = new ExampleParser( 'string to parse' ) ;
$res = $x->match_Expr() ;
</code></pre>
### Parser Format
Parsers are contained within a PHP file, in one or more special comment blocks that start with `/*!* [name | !pragma]` (like a docblock, but with an
exclamation mark in the middle of the stars)
You can have multiple comment blocks, all of which are treated as contiguous for the purpose of compiling. During compilation these blocks will be replaced
with a set of "matching" functions (functions which match a string against their rules) for each rule in the block.
The optional name marks the start of a new set of parser rules. This is currently unused, but might be used in future for opimization & debugging purposes.
If unspecified, it defaults to the same name as the previous parser comment block, or 'Anonymous Parser' if no name has ever been set.
If the name starts with an '!' symbol, that comment block is a pragma, and is treated not as some part of the parser, but as a special block of meta-data
Lexically, these blocks are a set of rules & comments. A rule can be a base rule or an extension rule
##### Base rules
Base rules consist of a name for the rule, some optional arguments, the matching rule itself, and an optional set of attached functions
NAME ( "(" ARGUMENT, ... ")" )? ":" MATCHING_RULE
ATTACHED_FUNCTIONS?
Names must be the characters a-z, A-Z, 0-9 and _ only, and must not start with a number
Base rules can be split over multiple lines as long as subsequent lines are indented
##### Extension rules
Extension rules are either the same as a base rule but with an addition name of the rule to extend, or as a replacing extension consist of
a name for the rule, the name of the rule to extend, and optionally: some arguments, some replacements, and a set of attached functions
NAME extend BASENAME ( "(" ARGUMENT, ... ")" )? ":" MATCHING_RULE
ATTACHED_FUNCTIONS?
NAME extends BASENAME ( "(" ARGUMENT, ... ")" )? ( ";" REPLACE "=>" REPLACE_WITH, ... )?
ATTACHED_FUNCTIONS?
##### Tricks and traps
We allow indenting a parser block, but only in a consistant manner - whatever the indent of the /*** marker becomes the "base" indent, and needs to be used
for all lines. You can mix tabs and spaces, but the indent must always be an exact match - if the "base" indent is a tab then two spaces, every line within the
block also needs indenting with a tab then two spaces, not two tabs (even if in your editor, that gives the same indent).
Any line with more than the "base" indent is considered a continuation of the previous rule
Any line with less than the "base" indent is an error
This might get looser if I get around to re-writing the internal "parser parser" in php-peg, bootstrapping the whole thing
### Rules
PEG matching rules try to follow standard PEG format, summarised thusly:
<pre><code>
token* - Token is optionally repeated
token+ - Token is repeated at least one
token? - Token is optionally present
tokena tokenb - Token tokenb follows tokena, both of which are present
tokena | tokenb - One of tokena or tokenb are present, prefering tokena
&token - Token is present next (but not consumed by parse)
!token - Token is not present next (but not consumed by parse)
( expression ) - Grouping for priority
</code></pre>
But with these extensions:
<pre><code>
< or > - Optionally match whitespace
[ or ] - Require some whitespace
</code></pre>
### Tokens
Tokens may be
- bare-words, which are recursive matchers - references to token rules defined elsewhere in the grammar,
- literals, surrounded by `"` or `'` quote pairs. No escaping support is provided in literals.
- regexs, surrounded by `/` pairs.
- expressions - single words (match \w+) starting with `$` or more complex surrounded by `${ }` which call a user defined function to perform the match
##### Regular expression tokens
Automatically anchored to the current string start - do not include a string start anchor (`^`) anywhere. Always acts as when the 'x' flag is enabled in PHP -
whitespace is ignored unless escaped, and '#' stats a comment.
Be careful when ending a regular expression token - the '*/' pattern (as in /foo\s*/) will end a PHP comment. Since the 'x' flag is always active,
just split with a space (as in / foo \s* /)
### Expressions
Expressions allow run-time calculated matching. You can embed an expression within a literal or regex token to
match against a calculated value, or simply specify the expression as a token to match against a dynamic rule.
#### Expression stack
When getting a value to use for an expression, the parser will travel up the stack looking for a set value. The expression
stack is a list of all the rules passed through to get to this point. For example, given the parser
<pre><code>
A: $a
B: A
C: B
</code></pre>
The expression stack for finding $a will be C, B, A - in other words, the A rule will be checked first, followed by B, followed by C
#### In terminals (literals and regexes)
The token will be replaced by the looked up value. To find the value for the token, the expression stack will be
travelled up checking for one of the following:
- A key / value pair in the result array node
- A rule-attached method INCLUDING `$` ( i.e. `function $foo()` )
If no value is found it will then check if a method or a property excluding the $ exists on the parser. If neither of those is found
the expression will be replaced with an exmpty string/
#### As tokens
The token will be looked up to find a value, which must be the name of a matching rule. That rule will then be matched
against as if the token was a recurse token for that rule.
To find the name of the rule to match against, the expression stack will be travelled up checking for one of the following:
- A key / value pair in the result array node
- A rule-attached method INCLUDING `$` ( i.e. `function $foo()` )
If no value is found it will then check if a method or a property excluding the $ exists on the parser. If neither of those if found
the rule will fail to match.
#### Tricks and traps
Be careful against using a token expression when you meant to use a terminal expression
<pre><code>
quoted_good: q:/['"]/ string "$q"
quoted_bad: q:/['"]/ string $q
</code></pre>
`"$q"` matches against the value of q again. `$q` tries to match against a rule named `"` or `'` (both of which are illegal rule
names, and will therefore fail)
### Named matching rules
Tokens and groups can be given names by prepending name and `:`, e.g.,
<pre><code>
rulea: "'" name:( tokena tokenb )* "'"
</code></pre>
There must be no space betweeen the name and the `:`
<pre><code>
badrule: "'" name : ( tokena tokenb )* "'"
</code></pre>
Recursive matchers can be given a name the same as their rule name by prepending with just a `:`. These next two rules are equivilent
<pre><code>
rulea: tokena tokenb:tokenb
rulea: tokena :tokenb
</code></pre>
### Rule-attached functions
Each rule can have a set of functions attached to it. These functions can be defined
- in-grammar by indenting the function body after the rule
- in-class after close of grammar comment by defining a regular method who's name is `{$rulename}_{$functionname}`, or `{$rulename}{$functionname}` if function name starts with `_`
- in a sub class
All functions that are not in-grammar must have PHP compatible names (see PHP name mapping). In-grammar functions will have their names converted if needed.
All these definitions define the same rule-attached function
<pre><code>
class A extends Parser {
/*!* Parser
foo: bar baz
function bar() {}
*/
function foo_bar() {}
}
class B extends A {
function foo_bar() {}
}
</code></pre>
### PHP name mapping
Rules in the grammar map to php functions named `match_{$rulename}`. However rule names can contain characters that php functions can't.
These characters are remapped:
<pre><code>
'-' => '_'
'$' => 'DLR'
'*' => 'STR'
</code></pre>
Other dis-allowed characters are removed.
## Results
Results are a tree of nested arrays.
Without any specific control, each rules result will just be the text it matched against in a `['text']` member. This member must always exist.
Marking a subexpression, literal, regex or recursive match with a name (see Named matching rules) will insert a member into the
result array named that name. If there is only one match it will be a single result array. If there is more than one match it will be an array of arrays.
You can override result storing by specifying a rule-attached function with the given name. It will be called with a reference to the current result array
and the sub-match - in this case the default storage action will not occur.
If you specify a rule-attached function for a recursive match, you do not need to name that token at all - it will be call automatically. E.g.
<pre><code>
rulea: tokena tokenb
function tokenb ( &$res, $sub ) { print 'Will be called, even though tokenb is not named or marked with a :' ; }
</code></pre>
You can also specify a rule-attached function called `*`, which will be called with every recursive match made
<pre><code>
rulea: tokena tokenb
function * ( &$res, $sub ) { print 'Will be called for both tokena and tokenb' ; }
</code></pre>
### Silent matches
By default all matches are added to the 'text' property of a result. By prepending a member with `.` that match will not be added to the ['text'] member. This
doesn't affect the other result properties that named rules' add.
### Inheritance
Rules can inherit off other rules using the keyword extends. There are several ways to change the matching of the rule, but
they all share a common feature - when building a result set the rule will also check the inherited-from rule's rule-attached
functions for storage handlers. This lets you do something like
<pre><code>
A: Foo Bar Baz
function *(){ /* Generic store handler */ }
B extends A
function Bar(){ /* Custom handling for Bar - Foo and Baz will still fall through to the A#* function defined above */ }
</code></pre>
The actual matching rule can be specified in three ways:
#### Duplication
If you don't specify a new rule or a replacement set the matching rule is copied as is. This is useful when you want to
override some storage logic but not the rule itself
#### Text replacement
You can replace some parts of the inherited rule using test replacement by using a ';' instead of an ':' after the name
of the extended rule. You can then put replacements in a comma seperated list. An example might help
<pre><code>
A: Foo | Bar | Baz
# Makes B the equivalent of Foo | Bar | (Baz | Qux)
B extends A: Baz => (Baz | Qux)
</code></pre>
Note that the replacements are not quoted. The exception is when you want to replace with the empty string, e.g.
<pre><code>
A: Foo | Bar | Baz
# Makes B the equivalent of Foo | Bar
B extends A: | Baz => ""
</code></pre>
Currently there is no escaping supported - if you want to replace "," or "=>" characters you'll have to use full replacement
#### Full replacement
You can specify an entirely new rule in the same format as a non-inheriting rule, eg.
<pre><code>
A: Foo | Bar | Baz
B extends A: Foo | Bar | (Baz Qux)
</code></pre>
This is useful is the rule changes too much for text replacement to be readable, but want to keep the storage logic
### Pragmas
When opening a parser comment block, if instead of a name (or no name) you put a word starting with '!', that comment block is treated as a pragma - not
part of the parser language itself, but some other instruction to the compiler. These pragmas are currently understood:
!silent
This is a comment that should only appear in the source code. Don't output it in the generated code
!insert_autogen_warning
Insert a warning comment into the generated code at this point, warning that the file is autogenerated and not to edit it
## TODO
- Allow configuration of whitespace - specify what matches, and wether it should be injected into results as-is, collapsed, or not at all
- Allow inline-ing of rules into other rules for speed
- More optimisation
- Make Parser-parser be self-generated, instead of a bad hand rolled parser like it is now.
- PHP token parser, and other token streams, instead of strings only like now

5
thirdparty/php-peg/cli.php vendored Normal file
View File

@ -0,0 +1,5 @@
<?php
require 'Compiler.php' ;
ParserCompiler::cli( $_SERVER['argv'] ) ;

View File

@ -0,0 +1,25 @@
<?php
require '../Parser.php' ;
class CalculatedLiterals extends Parser {
/*!* CalculatedLiterals
string: ( /\\./ | /[^${parent.q}]/ )*
simplequote: q:/['"]/ string '$q'
freequote-matched: "qq" q:/[{\[(<]/ string '$matched'
function $matched( $res ) {
$a = array( '{' => '}', '[' => ']', '(' => ')', '<' => '>' ) ;
return $a[$res['q']] ;
}
freequote-unmatched: "qq" q:/./ string '$q'
quoted-string: freequote-matched | freequote-unmatched | simplequote
*/
}

View File

@ -0,0 +1,63 @@
<?php
require '../Parser.php' ;
class Calculator extends Parser {
/*!* Calculator
Number: /[0-9]+/
Value: Number > | '(' > Expr > ')' >
function Number( &$result, $sub ) {
$result['val'] = $sub['text'] ;
}
function Expr( &$result, $sub ) {
$result['val'] = $sub['val'] ;
}
Times: '*' > operand:Value >
Div: '/' > operand:Value >
Product: Value > ( Times | Div ) *
function Value( &$result, $sub ) {
$result['val'] = $sub['val'] ;
}
function Times( &$result, $sub ) {
$result['val'] *= $sub['operand']['val'] ;
}
function Div( &$result, $sub ) {
$result['val'] /= $sub['operand']['val'] ;
}
Plus: '+' > operand:Product >
Minus: '-' > operand:Product >
Sum: Product > ( Plus | Minus ) *
function Product( &$result, $sub ) {
$result['val'] = $sub['val'] ;
}
function Plus( &$result, $sub ) {
$result['val'] += $sub['operand']['val'] ;
}
function Minus( &$result, $sub ) {
$result['val'] -= $sub['operand']['val'] ;
}
Expr: Sum
function Sum( &$result, $sub ) {
$result['val'] = $sub['val'] ;
}
*/
}
$x = new Calculator( '(2 + 4) * 3 - 10' ) ;
$res = $x->match_Expr() ;
if ( $res === FALSE ) {
print "No Match\n" ;
}
else {
print_r( $res ) ;
}

View File

@ -0,0 +1,36 @@
<?php
require '../Parser.php' ;
class EqualRepeat extends Packrat {
/* Any number of a followed by the same number of b and the same number of c characters
* aabbcc - good
* aaabbbccc - good
* aabbc - bad
* aabbacc - bad
*/
/*!* Grammar1
A: "a" A? "b"
B: "b" B? "c"
T: !"b"
X: &(A !"b") "a"+ B !("a" | "b" | "c")
*/
}
function match( $str ) {
$p = new EqualRepeat( $str ) ;
$r = $p->match_X() ;
print "$str\n" ;
print $r ? print_r( $r, true ) : 'No Match' ;
print "\n\n" ;
}
match( 'aabbcc' ) ; // Should match
match( 'aaabbbccc' ) ; // Should match
match( 'aabbbccc' ) ; // Should not match
match( 'aaabbccc' ) ; // Should not match
match( 'aaabbbcc' ) ; // Should not match
match( 'aaabbbcccc' ) ; // Should not match

View File

@ -0,0 +1,88 @@
<?php
require '../Parser.php';
/**
* This parser strictly matches the RFC822 standard. No characters outside the ASCII range 0-127 are allowed
* @author Hamish Friedlander
*/
class Rfc822 extends Parser {
/*!* Rfc822
crlf: /\r\n/
lwsp-char: " " | "\t"
linear-white-space: (crlf? lwsp-char)+
atom: /[^\x00-\x1F\x20()<>@,;:\\".\[\]\x80-\xFF]+/
qtext-chars: /[^"\\\x0D]+/
qtext: linear-white-space | qtext-chars
quoted-pair: /\\[\x00-\x7F]/
quoted-string: .'"' ( quoted-pair | qtext )* .'"'
word: atom | quoted-string
phrase: (word >)+
dtext-chars: /[^\[\]\\\r]+/
dtext: linear-white-space | dtext-chars
domain-literal: "[" ( dtext | quoted-pair )* "]"
domain-ref: atom
sub-domain: domain-ref | domain-literal
domain: sub-domain ("." sub-domain)*
route: "@" domain ("," "@" domain)* ":"
route-addr: "<" route? addr-spec ">"
function addr_spec ( &$self, $sub ) {
$self['addr_spec'] = $sub['text'] ;
}
local-part: word ("." word)*
addr-spec: local-part "@" domain
mailbox: ( addr-spec | phrase route-addr ) >
function __construct( &$self ) {
$self['phrase'] = NULL ;
$self['address'] = NULL ;
}
function phrase ( &$self, $sub ) {
$self['phrase'] = $sub['text'] ;
}
function addr_spec ( &$self, $sub ) {
$self['address'] = $sub['text'] ;
}
function route_addr ( &$self, $sub ) {
$self['address'] = $sub['addr_spec'] ;
}
group: phrase ":" ( mailbox ("," mailbox)* )? ";"
address: :mailbox | group
address-header: address (<","> address)*
function __construct( &$self ) {
$self['addresses'] = array() ;
}
function address( &$self, $sub ) {
$self['addresses'][] = $sub['mailbox'] ;
}
*/
}
$p = new Rfc822( 'John Byorgson <byorn@again.com>, "Akira \"Bad Boy\" Kenada" <akira@neotokyo.com>' ) ;
print_r( $p->match_address_header() ) ;

View File

@ -0,0 +1,30 @@
<?php
require 'Rfc822.php';
/**
* This parser extends the RFC822 standard to allow XML entities and UTF-8 characters in atoms and quoted-strings
* @author Hamish Friedlander
*/
class Rfc822UTF8 extends Rfc822 {
/*!* Rfc822UTF8
crlf: /\r\n/u
atom: /((&[A-Za-z]+;)|(&#(xX)?[A-Fa-f0-9]+;)|([^\x00-\x1F\x20()<>@,;:\\".\[\]]))+/u
qtext-chars: /[^"\\\x0D]+/u
quoted-pair: /\\./u
*/
}
/**
* Some trial code. Remove soon
*/
$p = new Rfc822UTF8( 'JØhn ByØrgsØn <byorn@again.com>, "アキラ" <akira@neotokyo.com>' ) ;
print_r( $p->match_address_header() ) ;
/* */

View File

@ -0,0 +1,123 @@
<?php
require_once "ParserTestBase.php";
class ParserInheritanceTest extends ParserTestBase {
public function testBasicInheritance() {
$parser = $this->buildParser('
/*!* BasicInheritanceTestParser
Foo: "a"
Bar extends Foo
*/
');
$this->assertTrue($parser->matches('Foo', 'a'));
$this->assertTrue($parser->matches('Bar', 'a'));
$this->assertFalse($parser->matches('Foo', 'b'));
$this->assertFalse($parser->matches('Bar', 'b'));
}
public function testBasicInheritanceConstructFallback() {
$parser = $this->buildParser('
/*!* BasicInheritanceConstructFallbackParser
Foo: "a"
function __construct(&$res){ $res["test"] = "test"; }
Bar extends Foo
*/
');
$res = $parser->match('Foo', 'a');
$this->assertEquals($res['test'], 'test');
$res = $parser->match('Bar', 'a');
$this->assertEquals($res['test'], 'test');
$parser = $this->buildParser('
/*!* BasicInheritanceConstructFallbackParser2
Foo: "a"
function __construct(&$res){ $res["testa"] = "testa"; }
Bar extends Foo
function __construct(&$res){ $res["testb"] = "testb"; }
*/
');
$res = $parser->match('Foo', 'a');
$this->assertArrayHasKey('testa', $res);
$this->assertEquals($res['testa'], 'testa');
$this->assertArrayNotHasKey('testb', $res);
$res = $parser->match('Bar', 'a');
$this->assertArrayHasKey('testb', $res);
$this->assertEquals($res['testb'], 'testb');
$this->assertArrayNotHasKey('testa', $res);
}
public function testBasicInheritanceStoreFallback() {
$parser = $this->buildParser('
/*!* BasicInheritanceStoreFallbackParser
Foo: Pow:"a"
function *(&$res, $sub){ $res["test"] = "test"; }
Bar extends Foo
*/
');
$res = $parser->match('Foo', 'a');
$this->assertEquals($res['test'], 'test');
$res = $parser->match('Bar', 'a');
$this->assertEquals($res['test'], 'test');
$parser = $this->buildParser('
/*!* BasicInheritanceStoreFallbackParser2
Foo: Pow:"a" Zap:"b"
function *(&$res, $sub){ $res["testa"] = "testa"; }
Bar extends Foo
function *(&$res, $sub){ $res["testb"] = "testb"; }
Baz extends Foo
function Zap(&$res, $sub){ $res["testc"] = "testc"; }
*/
');
$res = $parser->match('Foo', 'ab');
$this->assertArrayHasKey('testa', $res);
$this->assertEquals($res['testa'], 'testa');
$this->assertArrayNotHasKey('testb', $res);
$res = $parser->match('Bar', 'ab');
$this->assertArrayHasKey('testb', $res);
$this->assertEquals($res['testb'], 'testb');
$this->assertArrayNotHasKey('testa', $res);
$res = $parser->match('Baz', 'ab');
$this->assertArrayHasKey('testa', $res);
$this->assertEquals($res['testa'], 'testa');
$this->assertArrayHasKey('testc', $res);
$this->assertEquals($res['testc'], 'testc');
$this->assertArrayNotHasKey('testb', $res);
}
public function testInheritanceByReplacement() {
$parser = $this->buildParser('
/*!* InheritanceByReplacementParser
A: "a"
B: "b"
Foo: A B
Bar extends Foo; B => A
Baz extends Foo; A => ""
*/
');
$parser->assertMatches('Foo', 'ab');
$parser->assertMatches('Bar', 'aa');
$parser->assertMatches('Baz', 'b');
}
}

View File

@ -0,0 +1,26 @@
<?php
require_once "ParserTestBase.php";
class ParserSyntaxTest extends ParserTestBase {
public function testBasicRuleSyntax() {
$parser = $this->buildParser('
/*!* BasicRuleSyntax
Foo: "a" "b"
Bar: "a"
"b"
Baz:
"a" "b"
Qux:
"a"
"b"
*/
');
$parser->assertMatches('Foo', 'ab');
$parser->assertMatches('Bar', 'ab');
$parser->assertMatches('Baz', 'ab');
$parser->assertMatches('Qux', 'ab');
}
}

View File

@ -0,0 +1,48 @@
<?php
$base = dirname(dirname(__FILE__));
include "$base/Compiler.php";
include "$base/Parser.php";
class ParserTestWrapper {
function __construct($testcase, $class){
$this->testcase = $testcase;
$this->class = $class;
}
function match($method, $string, $allowPartial = false){
$class = $this->class;
$func = 'match_'.$method;
$parser = new $class($string);
$res = $parser->$func();
return ($allowPartial || $parser->pos == strlen($string)) ? $res : false;
}
function matches($method, $string, $allowPartial = false){
return $this->match($method, $string, $allowPartial) !== false;
}
function assertMatches($method, $string, $message = null){
$this->testcase->assertTrue($this->matches($method, $string), $message ? $message : "Assert parser method $method matches string $string");
}
function assertDoesntMatch($method, $string, $message = null){
$this->testcase->assertFalse($this->matches($method, $string), $message ? $message : "Assert parser method $method doesn't match string $string");
}
}
class ParserTestBase extends PHPUnit_Framework_TestCase {
function buildParser($parser) {
$class = 'Parser'.sha1($parser);
echo ParserCompiler::compile("class $class extends Parser {\n $parser\n}") . "\n\n\n";
eval(ParserCompiler::compile("class $class extends Parser {\n $parser\n}"));
return new ParserTestWrapper($this, $class);
}
}

View File

@ -0,0 +1,55 @@
<?php
require_once "ParserTestBase.php";
class ParserVariablesTest extends ParserTestBase {
public function testBasicLiteralVariables() {
$parser = $this->buildParser('
/*!* BasicVariables
Foo: Letter:"a" "$Letter"
Bar: Letter:"b" "$Letter $Letter"
Baz: Letter:"c" "$Letter a $Letter a"
Qux: Letter:"d" "{$Letter}a{$Letter}a"
*/
');
$parser->assertMatches('Foo', 'aa');
$parser->assertMatches('Bar', 'bb b');
$parser->assertMatches('Baz', 'cc a c a');
$parser->assertMatches('Qux', 'ddada');
}
public function testRecurseOnVariables() {
$parser = $this->buildParser('
/*!* RecurseOnVariablesParser
A: "a"
B: "b"
Foo: $Template
Bar: Foo
function __construct(&$res){ $res["Template"] = "A"; }
Baz: Foo
function __construct(&$res){ $res["Template"] = "B"; }
*/
');
$parser->assertMatches('Bar', 'a'); $parser->assertDoesntMatch('Bar', 'b');
$parser->assertMatches('Baz', 'b'); $parser->assertDoesntMatch('Baz', 'a');
}
public function testSetOnRuleVariables() {
$parser = $this->buildParser('
/*!* SetOnRuleVariablesParser
A: "a"
B: "b"
Foo: $Template
Bar (Template = A): Foo
Baz (Template = B): Foo
*/
');
$parser->assertMatches('Bar', 'a');
$parser->assertMatches('Baz', 'b');
}
}