/src/Opulence/Console/Responses/Compilers/Lexers/Lexer.php

https://github.com/opulencephp/Opulence · PHP · 181 lines · 116 code · 22 blank · 43 comment · 23 complexity · f954ef6c1d85d1ff7494dbc7e74236ab MD5 · raw file

  1. <?php
  2. /*
  3. * Opulence
  4. *
  5. * @link https://www.opulencephp.com
  6. * @copyright Copyright (C) 2017 David Young
  7. * @license https://github.com/opulencephp/Opulence/blob/master/LICENSE.md
  8. */
  9. namespace Opulence\Console\Responses\Compilers\Lexers;
  10. use Opulence\Console\Responses\Compilers\Lexers\Tokens\Token;
  11. use Opulence\Console\Responses\Compilers\Lexers\Tokens\TokenTypes;
  12. use RuntimeException;
  13. /**
  14. * Defines the response lexer
  15. */
  16. class Lexer implements ILexer
  17. {
  18. /**
  19. * @inheritdoc
  20. */
  21. public function lex(string $text) : array
  22. {
  23. $tokens = [];
  24. $wordBuffer = '';
  25. $elementNameBuffer = '';
  26. $inOpenTag = false;
  27. $inCloseTag = false;
  28. $charArray = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
  29. $textLength = count($charArray);
  30. foreach ($charArray as $charIter => $char) {
  31. switch ($char) {
  32. case '<':
  33. if ($this->lookBehind($charArray, $charIter) === '\\') {
  34. // This tag was escaped
  35. // Don't include the preceding slash
  36. $wordBuffer = mb_substr($wordBuffer, 0, -1) . $char;
  37. } elseif ($inOpenTag || $inCloseTag) {
  38. throw new RuntimeException(
  39. sprintf(
  40. 'Invalid tags near "%s", character #%d',
  41. $this->getSurroundingText($charArray, $charIter),
  42. $charIter
  43. )
  44. );
  45. } else {
  46. // Check if this is a closing tag
  47. if ($this->peek($charArray, $charIter) === '/') {
  48. $inCloseTag = true;
  49. $inOpenTag = false;
  50. } else {
  51. $inCloseTag = false;
  52. $inOpenTag = true;
  53. }
  54. // Flush the word buffer
  55. if ($wordBuffer !== '') {
  56. $tokens[] = new Token(
  57. TokenTypes::T_WORD,
  58. $wordBuffer,
  59. $charIter - mb_strlen($wordBuffer)
  60. );
  61. $wordBuffer = '';
  62. }
  63. }
  64. break;
  65. case '>':
  66. if ($inOpenTag || $inCloseTag) {
  67. if ($inOpenTag) {
  68. $tokens[] = new Token(
  69. TokenTypes::T_TAG_OPEN,
  70. $elementNameBuffer,
  71. // Need to get the position of the beginning of the open tag
  72. $charIter - mb_strlen($elementNameBuffer) - 1
  73. );
  74. } else {
  75. $tokens[] = new Token(
  76. TokenTypes::T_TAG_CLOSE,
  77. $elementNameBuffer,
  78. // Need to get the position of the beginning of the close tag
  79. $charIter - mb_strlen($elementNameBuffer) - 2
  80. );
  81. }
  82. $elementNameBuffer = '';
  83. $inOpenTag = false;
  84. $inCloseTag = false;
  85. } else {
  86. $wordBuffer .= $char;
  87. }
  88. break;
  89. default:
  90. if ($inOpenTag || $inCloseTag) {
  91. // We're in a tag, so buffer the element name
  92. if ($char !== '/') {
  93. $elementNameBuffer .= $char;
  94. }
  95. } else {
  96. // We're outside of a tag somewhere
  97. $wordBuffer .= $char;
  98. }
  99. break;
  100. }
  101. }
  102. // Finish flushing the word buffer
  103. if ($wordBuffer !== '') {
  104. $tokens[] = new Token(
  105. TokenTypes::T_WORD,
  106. $wordBuffer,
  107. $textLength - mb_strlen($wordBuffer)
  108. );
  109. }
  110. $tokens[] = new Token(TokenTypes::T_EOF, null, $textLength);
  111. return $tokens;
  112. }
  113. /**
  114. * Gets text around a certain position for use in exceptions
  115. *
  116. * @param array $charArray The char array
  117. * @param int $position The numerical position to grab text around
  118. * @return string The surrounding text
  119. */
  120. private function getSurroundingText(array $charArray, int $position) : string
  121. {
  122. if (count($charArray) <= 3) {
  123. return implode('', $charArray);
  124. }
  125. if ($position <= 3) {
  126. return implode('', array_slice($charArray, 0, 4));
  127. }
  128. return implode('', array_slice($charArray, $position - 3, 4));
  129. }
  130. /**
  131. * Looks back at the previous character in the string
  132. *
  133. * @param array $charArray The char array
  134. * @param int $currPosition The current position
  135. * @return string|null The previous character if there is one, otherwise null
  136. */
  137. private function lookBehind(array $charArray, int $currPosition)
  138. {
  139. if ($currPosition === 0 || count($charArray) === 0) {
  140. return null;
  141. }
  142. return $charArray[$currPosition - 1];
  143. }
  144. /**
  145. * Peeks at the next character in the string
  146. *
  147. * @param array $charArray The char array
  148. * @param int $currPosition The current position
  149. * @return string|null The next character if there is one, otherwise null
  150. */
  151. private function peek(array $charArray, int $currPosition)
  152. {
  153. $charArrayLength = count($charArray);
  154. if ($charArrayLength === 0 || $charArrayLength === $currPosition + 1) {
  155. return null;
  156. }
  157. return $charArray[$currPosition + 1];
  158. }
  159. }