/vendor/phug/phug/src/Phug/Lexer/Lexer/Scanner/InterpolationScanner.php

https://github.com/welaika/wordless · PHP · 167 lines · 121 code · 36 blank · 10 comment · 8 complexity · f1dce95d42b1a1c3118a1e252f9780c9 MD5 · raw file

  1. <?php
  2. /**
  3. * @example p Text #{'interpolation'} text
  4. */
  5. namespace Phug\Lexer\Scanner;
  6. use Phug\Lexer\ScannerInterface;
  7. use Phug\Lexer\State;
  8. use Phug\Lexer\Token\ExpressionToken;
  9. use Phug\Lexer\Token\InterpolationEndToken;
  10. use Phug\Lexer\Token\InterpolationStartToken;
  11. use Phug\Lexer\Token\NewLineToken;
  12. use Phug\Lexer\Token\TagInterpolationEndToken;
  13. use Phug\Lexer\Token\TagInterpolationStartToken;
  14. use Phug\Lexer\Token\TextToken;
  15. class InterpolationScanner implements ScannerInterface
  16. {
  17. protected $interpolationChars = [
  18. 'tagInterpolation' => ['[', ']'],
  19. 'interpolation' => ['{', '}'],
  20. ];
  21. protected $regExp;
  22. public function __construct()
  23. {
  24. $interpolations = [];
  25. $backIndex = 2;
  26. foreach ($this->interpolationChars as $name => list($start, $end)) {
  27. $start = preg_quote($start, '/');
  28. $end = preg_quote($end, '/');
  29. $interpolations[] = $start.'(?<'.$name.'>'.
  30. '(?>"(?:\\\\[\\S\\s]|[^"\\\\])*"|\'(?:\\\\[\\S\\s]|[^\'\\\\])*\'|[^'.
  31. $start.$end.
  32. '\'"]++|(?-'.$backIndex.'))*+'.
  33. ')'.$end;
  34. $backIndex++;
  35. }
  36. $this->regExp = '(?<text>.*?)'.
  37. '(?<!\\\\)'.
  38. '(?<escape>#|!(?='.preg_quote($this->interpolationChars['interpolation'][0], '/').'))'.
  39. '(?<wrap>'.implode('|', $interpolations).')';
  40. }
  41. protected function throwEndOfLineExceptionIf(State $state, $condition)
  42. {
  43. if ($condition) {
  44. $state->throwException('End of line was reached with no closing bracket for interpolation.');
  45. }
  46. }
  47. protected function scanTagInterpolation(State $state, $tagInterpolation)
  48. {
  49. /** @var TagInterpolationStartToken $start */
  50. $start = $state->createToken(TagInterpolationStartToken::class);
  51. /** @var TagInterpolationEndToken $end */
  52. $end = $state->createToken(TagInterpolationEndToken::class);
  53. $start->setEnd($end);
  54. $end->setStart($start);
  55. $lexer = $state->getLexer();
  56. yield $start;
  57. foreach ($lexer->lex($tagInterpolation) as $token) {
  58. $this->throwEndOfLineExceptionIf($state, $token instanceof NewLineToken);
  59. yield $token;
  60. }
  61. yield $end;
  62. }
  63. protected function scanExpressionInterpolation(State $state, $interpolation, $escape)
  64. {
  65. /** @var InterpolationStartToken $start */
  66. $start = $state->createToken(InterpolationStartToken::class);
  67. /** @var InterpolationEndToken $end */
  68. $end = $state->createToken(InterpolationEndToken::class);
  69. $start->setEnd($end);
  70. $end->setStart($start);
  71. /** @var ExpressionToken $token */
  72. $token = $state->createToken(ExpressionToken::class);
  73. $token->setValue($interpolation);
  74. if ($escape === '#') {
  75. $token->escape();
  76. }
  77. yield $start;
  78. yield $token;
  79. yield $end;
  80. }
  81. protected function scanInterpolation(State $state, $tagInterpolation, $interpolation, $escape)
  82. {
  83. $this->throwEndOfLineExceptionIf(
  84. $state,
  85. !$state->getOption('multiline_interpolation') && strpos($interpolation, "\n") !== false
  86. );
  87. if ($tagInterpolation) {
  88. return $this->scanTagInterpolation($state, $tagInterpolation);
  89. }
  90. return $this->scanExpressionInterpolation($state, $interpolation, $escape);
  91. }
  92. protected function needSeparationBlankLine(State $state)
  93. {
  94. $reader = $state->getReader();
  95. if (!$reader->peekNewLine()) {
  96. return false;
  97. }
  98. $indentWidth = $state->getIndentWidth();
  99. $indentation = $indentWidth > 0 ? $state->getIndentStyle().'{'.$state->getIndentWidth().'}' : '';
  100. return $reader->match('\n*'.$indentation.'\|');
  101. }
  102. public function scan(State $state)
  103. {
  104. $reader = $state->getReader();
  105. while ($reader->match($this->regExp)) {
  106. $text = $reader->getMatch('text');
  107. $text = preg_replace('/\\\\([#!]\\[|#{)/', '$1', $text);
  108. if (mb_strlen($text) > 0) {
  109. /** @var TextToken $token */
  110. $token = $state->createToken(TextToken::class);
  111. $token->setValue($text);
  112. yield $token;
  113. }
  114. foreach ($this->scanInterpolation(
  115. $state,
  116. $reader->getMatch('tagInterpolation'),
  117. $reader->getMatch('interpolation'),
  118. $reader->getMatch('escape')
  119. ) as $token) {
  120. yield $token;
  121. }
  122. $reader->consume();
  123. if ($this->needSeparationBlankLine($state)) {
  124. /** @var TextToken $token */
  125. $token = $state->createToken(TextToken::class);
  126. $token->setValue("\n");
  127. yield $token;
  128. }
  129. }
  130. }
  131. }