protected $lineno;
protected $end;
protected $state;
+ protected $brackets;
protected $env;
protected $filename;
const STATE_BLOCK = 1;
const STATE_VAR = 2;
- const REGEX_NAME = '/[A-Za-z_][A-Za-z0-9_]*/A';
- const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
- const REGEX_STRING = '/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
- const REGEX_PUNCTUATION = '/[\[\](){}?:.,|]/A';
+ const REGEX_NAME = '/[A-Za-z_][A-Za-z0-9_]*/A';
+ const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
+ const REGEX_STRING = '/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
+ const PUNCTUATION = '()[]{}?:.,|';
public function __construct(Twig_Environment $env, array $options = array())
{
$this->end = strlen($this->code);
$this->tokens = array();
$this->state = self::STATE_DATA;
+ $this->brackets = array();
while ($this->cursor < $this->end) {
// dispatch to the lexing functions depending
$this->pushToken(Twig_Token::EOF_TYPE);
+ if (!empty($this->brackets)) {
+ list($expect, $lineno) = array_pop($this->brackets);
+ throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
+ }
+
if (isset($mbEncoding)) {
mb_internal_encoding($mbEncoding);
}
protected function lexBlock()
{
- if (preg_match('/\s*'.preg_quote($this->options['tag_block'][1], '/').'/A', $this->code, $match, null, $this->cursor)) {
+ if (empty($this->brackets) && preg_match('/\s*'.preg_quote($this->options['tag_block'][1], '/').'/A', $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::BLOCK_END_TYPE);
$this->moveCursor($match[0]);
$this->state = self::STATE_DATA;
protected function lexVar()
{
- if (preg_match('/\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', $this->code, $match, null, $this->cursor)) {
+ if (empty($this->brackets) && preg_match('/\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::VAR_END_TYPE);
$this->moveCursor($match[0]);
$this->state = self::STATE_DATA;
$this->moveCursor($match[0]);
}
// punctuation
- elseif (preg_match(self::REGEX_PUNCTUATION, $this->code, $match, null, $this->cursor)) {
- $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $match[0]);
- $this->moveCursor($match[0]);
+ elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
+ // opening bracket
+ if (false !== strpos('([{', $this->code[$this->cursor])) {
+ $this->brackets[] = array($this->code[$this->cursor], $this->lineno);
+ }
+ // closing bracket
+ elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
+ if (empty($this->brackets)) {
+ throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
+ }
+
+ list($expect, $lineno) = array_pop($this->brackets);
+ if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) {
+ throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
+ }
+ }
+
+ $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
+ ++$this->cursor;
}
// strings
elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) {
$regex = array();
foreach ($operators as $operator => $length) {
// an operator that ends with a character must be followed by
- // a whitespace or a parenthese
+ // a whitespace or a parenthesis
if (ctype_alpha($operator[$length - 1])) {
$regex[] = preg_quote($operator, '/').'(?=[ ()])';
} else {