PageRenderTime 25ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/vendor/twig/lib/Twig/Lexer.php

https://github.com/chotchki/symfony-sandbox
PHP | 317 lines | 217 code | 52 blank | 48 comment | 40 complexity | c3066599bcc3363604e361475cae2ff5 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) 2009 Fabien Potencier
  6. * (c) 2009 Armin Ronacher
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. /**
  12. * Lexes a template string.
  13. *
  14. * @package twig
  15. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  16. */
  17. class Twig_Lexer implements Twig_LexerInterface
  18. {
  19. protected $cursor;
  20. protected $position;
  21. protected $end;
  22. protected $pushedBack;
  23. protected $code;
  24. protected $lineno;
  25. protected $filename;
  26. protected $env;
  27. protected $options;
  28. const POSITION_DATA = 0;
  29. const POSITION_BLOCK = 1;
  30. const POSITION_VAR = 2;
  31. const REGEX_NAME = '/[A-Za-z_][A-Za-z0-9_]*/A';
  32. const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
  33. const REGEX_STRING = '/(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')/Asm';
  34. const REGEX_OPERATOR = '/<=? | >=? | [!=]= | = | \/\/ | \.\. | [(){}.,%*\/+~|-] | \[ | \] | \? | \:/Ax';
  35. public function __construct(Twig_Environment $env = null, array $options = array())
  36. {
  37. if (null !== $env) {
  38. $this->setEnvironment($env);
  39. }
  40. $this->options = array_merge(array(
  41. 'tag_comment' => array('{#', '#}'),
  42. 'tag_block' => array('{%', '%}'),
  43. 'tag_variable' => array('{{', '}}'),
  44. ), $options);
  45. }
  46. /**
  47. * Tokenizes a source code.
  48. *
  49. * @param string $code The source code
  50. * @param string $filename A unique identifier for the source code
  51. *
  52. * @return Twig_TokenStream A token stream instance
  53. */
  54. public function tokenize($code, $filename = 'n/a')
  55. {
  56. if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
  57. $mbEncoding = mb_internal_encoding();
  58. mb_internal_encoding('ASCII');
  59. }
  60. $this->code = str_replace(array("\r\n", "\r"), "\n", $code);
  61. $this->filename = $filename;
  62. $this->cursor = 0;
  63. $this->lineno = 1;
  64. $this->pushedBack = array();
  65. $this->end = strlen($this->code);
  66. $this->position = self::POSITION_DATA;
  67. $tokens = array();
  68. $end = false;
  69. while (!$end) {
  70. $token = $this->nextToken();
  71. $tokens[] = $token;
  72. $end = $token->getType() === Twig_Token::EOF_TYPE;
  73. }
  74. if (isset($mbEncoding)) {
  75. mb_internal_encoding($mbEncoding);
  76. }
  77. return new Twig_TokenStream($tokens, $this->filename, $this->env->getTrimBlocks());
  78. }
  79. public function setEnvironment(Twig_Environment $env)
  80. {
  81. $this->env = $env;
  82. }
  83. /**
  84. * Parses the next token and returns it.
  85. */
  86. protected function nextToken()
  87. {
  88. // do we have tokens pushed back? get one
  89. if (!empty($this->pushedBack)) {
  90. return array_shift($this->pushedBack);
  91. }
  92. // have we reached the end of the code?
  93. if ($this->cursor >= $this->end) {
  94. return new Twig_Token(Twig_Token::EOF_TYPE, '', $this->lineno);
  95. }
  96. // otherwise dispatch to the lexing functions depending
  97. // on our current position in the code.
  98. switch ($this->position) {
  99. case self::POSITION_DATA:
  100. $tokens = $this->lexData();
  101. break;
  102. case self::POSITION_BLOCK:
  103. $tokens = $this->lexBlock();
  104. break;
  105. case self::POSITION_VAR:
  106. $tokens = $this->lexVar();
  107. break;
  108. }
  109. // if the return value is not an array it's a token
  110. if (!is_array($tokens)) {
  111. return $tokens;
  112. }
  113. // empty array, call again
  114. else if (empty($tokens)) {
  115. return $this->nextToken();
  116. }
  117. // if we have multiple items we push them to the buffer
  118. else if (count($tokens) > 1) {
  119. $first = array_shift($tokens);
  120. $this->pushedBack = $tokens;
  121. return $first;
  122. }
  123. // otherwise return the first item of the array.
  124. else {
  125. return $tokens[0];
  126. }
  127. }
  128. protected function lexData()
  129. {
  130. $match = null;
  131. $pos1 = strpos($this->code, $this->options['tag_comment'][0], $this->cursor);
  132. $pos2 = strpos($this->code, $this->options['tag_variable'][0], $this->cursor);
  133. $pos3 = strpos($this->code, $this->options['tag_block'][0], $this->cursor);
  134. // if no matches are left we return the rest of the template
  135. // as simple text token
  136. if (false === $pos1 && false === $pos2 && false === $pos3) {
  137. $rv = new Twig_Token(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor), $this->lineno);
  138. $this->cursor = $this->end;
  139. return $rv;
  140. }
  141. // min
  142. $pos = -log(0);
  143. if (false !== $pos1 && $pos1 < $pos) {
  144. $pos = $pos1;
  145. $token = $this->options['tag_comment'][0];
  146. }
  147. if (false !== $pos2 && $pos2 < $pos) {
  148. $pos = $pos2;
  149. $token = $this->options['tag_variable'][0];
  150. }
  151. if (false !== $pos3 && $pos3 < $pos) {
  152. $pos = $pos3;
  153. $token = $this->options['tag_block'][0];
  154. }
  155. // update the lineno on the instance
  156. $lineno = $this->lineno;
  157. $text = substr($this->code, $this->cursor, $pos - $this->cursor);
  158. $this->moveCursor($text.$token);
  159. $this->moveLineNo($text.$token);
  160. // array of tokens
  161. $result = array();
  162. // push the template text first
  163. if (!empty($text)) {
  164. $result[] = new Twig_Token(Twig_Token::TEXT_TYPE, $text, $lineno);
  165. $lineno += substr_count($text, "\n");
  166. }
  167. switch ($token) {
  168. case $this->options['tag_comment'][0]:
  169. if (!preg_match('/(.*?)'.preg_quote($this->options['tag_comment'][1], '/').'/As', $this->code, $match, null, $this->cursor)) {
  170. throw new Twig_Error_Syntax('unclosed comment', $this->lineno, $this->filename);
  171. }
  172. $this->moveCursor($match[0]);
  173. $this->moveLineNo($match[0]);
  174. break;
  175. case $this->options['tag_block'][0]:
  176. // raw data?
  177. if (preg_match('/\s*raw\s*'.preg_quote($this->options['tag_block'][1], '/').'(.*?)'.preg_quote($this->options['tag_block'][0], '/').'\s*endraw\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) {
  178. $result[] = new Twig_Token(Twig_Token::TEXT_TYPE, $match[1], $lineno);
  179. $this->moveCursor($match[0]);
  180. $this->moveLineNo($match[0]);
  181. $this->position = self::POSITION_DATA;
  182. } else {
  183. $result[] = new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', $lineno);
  184. $this->position = self::POSITION_BLOCK;
  185. }
  186. break;
  187. case $this->options['tag_variable'][0]:
  188. $result[] = new Twig_Token(Twig_Token::VAR_START_TYPE, '', $lineno);
  189. $this->position = self::POSITION_VAR;
  190. break;
  191. }
  192. return $result;
  193. }
  194. protected function lexBlock()
  195. {
  196. if (preg_match('/\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) {
  197. $lineno = $this->lineno;
  198. $this->moveCursor($match[0]);
  199. $this->moveLineNo($match[0]);
  200. $this->position = self::POSITION_DATA;
  201. return new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $lineno);
  202. }
  203. return $this->lexExpression();
  204. }
  205. protected function lexVar()
  206. {
  207. if (preg_match('/\s*'.preg_quote($this->options['tag_variable'][1], '/').'/As', $this->code, $match, null, $this->cursor)) {
  208. $lineno = $this->lineno;
  209. $this->moveCursor($match[0]);
  210. $this->moveLineNo($match[0]);
  211. $this->position = self::POSITION_DATA;
  212. return new Twig_Token(Twig_Token::VAR_END_TYPE, '', $lineno);
  213. }
  214. return $this->lexExpression();
  215. }
  216. protected function lexExpression()
  217. {
  218. $match = null;
  219. // whitespace
  220. while (preg_match('/\s+/As', $this->code, $match, null, $this->cursor)) {
  221. $this->moveCursor($match[0]);
  222. $this->moveLineNo($match[0]);
  223. }
  224. // sanity check
  225. if ($this->cursor >= $this->end) {
  226. throw new Twig_Error_Syntax('Unexpected end of stream', $this->lineno, $this->filename);
  227. }
  228. // first parse operators
  229. if (preg_match(self::REGEX_OPERATOR, $this->code, $match, null, $this->cursor)) {
  230. $this->moveCursor($match[0]);
  231. return new Twig_Token(Twig_Token::OPERATOR_TYPE, $match[0], $this->lineno);
  232. }
  233. // now names
  234. else if (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) {
  235. $this->moveCursor($match[0]);
  236. return new Twig_Token(Twig_Token::NAME_TYPE, $match[0], $this->lineno);
  237. }
  238. // then numbers
  239. else if (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) {
  240. $this->moveCursor($match[0]);
  241. $value = (float)$match[0];
  242. if ((int)$value === $value) {
  243. $value = (int)$value;
  244. }
  245. return new Twig_Token(Twig_Token::NUMBER_TYPE, $value, $this->lineno);
  246. }
  247. // and finally strings
  248. else if (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) {
  249. $this->moveCursor($match[0]);
  250. $this->moveLineNo($match[0]);
  251. $value = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
  252. return new Twig_Token(Twig_Token::STRING_TYPE, $value, $this->lineno);
  253. }
  254. // unlexable
  255. throw new Twig_Error_Syntax(sprintf("Unexpected character '%s'", $this->code[$this->cursor]), $this->lineno, $this->filename);
  256. }
  257. protected function moveLineNo($text)
  258. {
  259. $this->lineno += substr_count($text, "\n");
  260. }
  261. protected function moveCursor($text)
  262. {
  263. $this->cursor += strlen($text);
  264. }
  265. }