<?php
class H2o_Lexer {
function __construct($options = array()) {
$this->options = $options;
$trim = '';
if ($this->options['TRIM_TAGS'])
$trim = '(?:\r?\n)?';
$this->pattern = ('/\G(.*?)(?:' .
preg_quote($this->options['BLOCK_START']). '(.*?)' .preg_quote($this->options['BLOCK_END']) . $trim . '|' .
preg_quote($this->options['VARIABLE_START']). '(.*?)' .preg_quote($this->options['VARIABLE_END']) . '|' .
preg_quote($this->options['COMMENT_START']). '(.*?)' .preg_quote($this->options['COMMENT_END']) . $trim . ')/sm'
);
}
function tokenize($source) {
$result = new TokenStream;
$pos = 0;
$matches = array();
preg_match_all($this->pattern, $source, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
if ($match[1])
$result->feed('text', $match[1], $pos);
$tagpos = $pos + strlen($match[1]);
if ($match[2])
$result->feed('block', trim($match[2]), $tagpos);
elseif ($match[3])
$result->feed('variable', trim($match[3]), $tagpos);
elseif ($match[4])
$result->feed('comment', trim($match[4]), $tagpos);
$pos += strlen($match[0]);
}
if ($pos < strlen($source)){
$result->feed('text', substr($source, $pos), $pos);
}
$result->close();
return $result;
}
}
class H2o_Parser {
var $first;
var $storage = array();
var $filename;
var $runtime;
function __construct($source, $filename, $runtime, $options) {
$this->options = $options;
//$this->source = $source;
$this->runtime = $runtime;
$this->filename = $filename;
$this->first = true;
$this->lexer = new H2o_Lexer($options);
$this->tokenstream = $this->lexer->tokenize($source);
$this->storage = array(
'blocks' => array(),
'templates' => array(),
'included' => array()
);
}
function &parse() {
$until = func_get_args();
$nodelist = new NodeList($this);
while($token = $this->tokenstream->next()) {
//$token = $this->tokenstream->current();
switch($token->type) {
case 'text' :
$node = new TextNode($token->content, $token->position);
break;
case 'variable' :
$args = H2o_Parser::parseArguments($token->content, $token->position);
$variable = array_shift($args);
$filters = $args;
$node = new VariableNode($variable, $filters, $token->position);
break;
case 'comment' :
$node = new CommentNode($token->content);
break;
case 'block' :
if (in_array($token->content, $until)) {
$this->token = $token;
return $nodelist;
}
$temp = preg_split('/\s+/',$token->content, 2);
$name = $temp[0];
$args = (count($temp) > 1 ? $temp[1] : null);
$node = H2o::createTag($name, $args, $this, $token->position);
$this->token = $token;
}
$this->searching = join(',',$until);
$this->first = false;
$nodelist->append($node);
}
if ($until) {
throw new TemplateSyntaxError('Unclose tag, expecting '. $until[0]);
}
return $nodelist;
}
function skipTo($until) {
$this->parse($until);
return null;
}
# Parse arguments
static function parseArguments($source = null, $fpos = 0){
$parser = new ArgumentLexer($source, $fpos);
$result = array();
$current_buffer = &$result;
$filter_buffer = array();
$tokens = $parser->parse();
foreach ($tokens as $token) {
list($token, $data) = $token;
if ($token == 'filter_start') {
$filter_buffer = array();
$current_buffer = &$filter_buffer;
}
elseif ($token == 'filter_end') {
if (count($filter_buffer)) {
$i = count($result)-1;
if ( is_array($result[$i]) ) $result[$i]['filters'][] = $filter_buffer;
else $result[$i] = array(0 => $result[$i], 'filters' => array($filter_buffer));
}
$current_buffer = &$result;
}
elseif ($token == 'boolean') {
$current_buffer[] = ($data === 'true'? true : false);
}
elseif ($token == 'name') {
$current_buffer[] = symbol($data);
}
elseif ($token == 'number' || $token == 'string') {
$current_buffer[] = $data;
}
elseif ($token == 'named_argument') {
$last = $current_buffer[count($current_buffer) - 1];
if (!is_array($last))
$current_buffer[] = array();
$namedArgs =& $current_buffer[count($current_buffer) - 1];
list($name,$value) = array_map('trim', explode(':', $data, 2));
# if argument value is variable mark it
$value = self::parseArguments($value);
$namedArgs[$name] = $value[0];
}
elseif( $token == 'operator') {
$current_buffer[] = array('operator'=>$data);
}
}
return $result;
}
}
class H2O_RE {
static $whitespace, $seperator, $parentheses, $pipe, $filter_end, $operator, $boolean, $number, $string, $i18n_string, $name, $named_args;
static function init() {
$r = 'strip_regex';
self::$whitespace = '/\s+/m';
self::$parentheses = '/\(|\)/m';
self::$filter_end = '/;/';
self::$boolean = '/true|false/';
self::$seperator = '/,/';
self::$pipe = '/\|/';
self::$operator = '/\s?(>|<|>=|<=|!=|==|!|and |not |or )\s?/i';
self::$number = '/\d+(\.\d*)?/';
self::$name = '/[a-zA-Z][a-zA-Z0-9-_]*(?:\.[a-zA-Z_0-9][a-zA-Z0-9_-]*)*/';
self::$string = '/(?:
"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)" | # Double Quote string
\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\' # Single Quote String
)/xsm';
self::$i18n_string = "/_\({$r(self::$string)}\) | {$r(self::$string)}/xsm";
self::$named_args = "{
({$r(self::$name)})(?:{$r(self::$whitespace)})?
:
(?:{$r(self::$whitespace)})?({$r(self::$i18n_string)}|{$r(self::$number)}|{$r(self::$name)})
}x";
}
}
H2O_RE::init();
class ArgumentLexer {
private $source;
private $match;
private $pos = 0, $fpos, $eos;
private $operator_map = array(
'!' => 'not', '!='=> 'ne', '==' => 'eq', '>' => 'gt', '<' => 'lt', '<=' => 'le', '>=' => 'ge'
);
function __construct($source, $fpos = 0){
if (!is_null($source))
$this->source = $source;
$this->fpos=$fpos;
}
function parse(){
$result = array();
$filtering = false;
while (!$this->eos()) {
$this->scan(H2O_RE::$whitespace);
if (!$filtering) {
if ($this->scan(H2O_RE::$operator)){
$operator = trim($this->match);
if(isset($this->operator_map[$operator]))
$operator = $this->operator_map[$operator];
$result[] = array('operator', $operator);
}
elseif ($this->scan(H2O_RE::$boolean))
$result[] = array('boolean', $this->match);
elseif ($this->scan(H2O_RE::$named_args))
$result[] = array('named_argument', $this->match);
elseif ($this->scan(H2O_RE::$name))
$result[] = array('name', $this->match);
elseif ($this->scan(H2O_RE::$pipe)) {
$filtering = true;
$result[] = array('filter_start', $this->match);
}
elseif ($this->scan(H2O_RE::$seperator))
$result[] = array('separator', null);
elseif ($this->scan(H2O_RE::$i18n_string))
$result[] = array('string', $this->match);
elseif ($this->scan(H2O_RE::$number))
$result[] = array('number', $this->match);
else
throw new TemplateSyntaxError('unexpected character in filters : "'. $this->source[$this->pos]. '" at '.$this->getPosition());
}
else {
// parse filters, with chaining and ";" as filter end character
if ($this->scan(H2O_RE::$pipe)) {
$result[] = array('filter_end', null);
$result[] = array('filter_start', null);
}
elseif ($this->scan(H2O_RE::$seperator))
$result[] = array('separator', null);
elseif ($this->scan(H2O_RE::$filter_end)) {
$result[] = array('filter_end', null);
$filtering = false;
}
elseif ($this->scan(H2O_RE::$boolean))
$result[] = array('boolean', $this->match);
elseif ($this->scan(H2O_RE::$named_args))
$result[] = array('named_argument', $this->match);
elseif ($this->scan(H2O_RE::$name))
$result[] = array('name', $this->match);
elseif ($this->scan(H2O_RE::$i18n_string))
$result[] = array('string', $this->match);
elseif ($this->scan(H2O_RE::$number))
$result[] = array('number', $this->match);
else
throw new TemplateSyntaxError('unexpected character in filters : "'. $this->source[$this->pos]. '" at '.$this->getPosition());
}
}
// if we are still in the filter state, we add a filter_end token.
if ($filtering)
$result[] = array('filter_end', null);
return $result;
}
# String scanner
function scan($regexp) {
if (preg_match($regexp . 'A', $this->source, $match, null, $this->pos)) {
$this->match = $match[0];
$this->pos += strlen($this->match);
return true;
}
return false;
}
function eos() {
return $this->pos >= strlen($this->source);
}
/**
* return the position in the template
*/
function getPosition() {
return $this->fpos + $this->pos;
}
}
?>