291 lines
11 KiB
PHP
291 lines
11 KiB
PHP
<?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;
|
|
}
|
|
}
|
|
?>
|