Emulative.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. namespace PhpParser\Lexer;
  3. use PhpParser\ErrorHandler;
  4. use PhpParser\Parser\Tokens;
  5. class Emulative extends \PhpParser\Lexer
  6. {
  7. protected $newKeywords;
  8. protected $inObjectAccess;
  9. const T_ELLIPSIS = 1001;
  10. const T_POW = 1002;
  11. const T_POW_EQUAL = 1003;
  12. const T_COALESCE = 1004;
  13. const T_SPACESHIP = 1005;
  14. const T_YIELD_FROM = 1006;
  15. const PHP_7_0 = '7.0.0dev';
  16. const PHP_5_6 = '5.6.0rc1';
  17. public function __construct(array $options = array()) {
  18. parent::__construct($options);
  19. $newKeywordsPerVersion = array(
  20. // No new keywords since PHP 5.5
  21. );
  22. $this->newKeywords = array();
  23. foreach ($newKeywordsPerVersion as $version => $newKeywords) {
  24. if (version_compare(PHP_VERSION, $version, '>=')) {
  25. break;
  26. }
  27. $this->newKeywords += $newKeywords;
  28. }
  29. if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
  30. return;
  31. }
  32. $this->tokenMap[self::T_COALESCE] = Tokens::T_COALESCE;
  33. $this->tokenMap[self::T_SPACESHIP] = Tokens::T_SPACESHIP;
  34. $this->tokenMap[self::T_YIELD_FROM] = Tokens::T_YIELD_FROM;
  35. if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
  36. return;
  37. }
  38. $this->tokenMap[self::T_ELLIPSIS] = Tokens::T_ELLIPSIS;
  39. $this->tokenMap[self::T_POW] = Tokens::T_POW;
  40. $this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
  41. }
  42. public function startLexing($code, ErrorHandler $errorHandler = null) {
  43. $this->inObjectAccess = false;
  44. parent::startLexing($code, $errorHandler);
  45. if ($this->requiresEmulation($code)) {
  46. $this->emulateTokens();
  47. }
  48. }
  49. /*
  50. * Checks if the code is potentially using features that require emulation.
  51. */
  52. protected function requiresEmulation($code) {
  53. if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
  54. return false;
  55. }
  56. if (preg_match('(\?\?|<=>|yield[ \n\r\t]+from)', $code)) {
  57. return true;
  58. }
  59. if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
  60. return false;
  61. }
  62. return preg_match('(\.\.\.|(?<!/)\*\*(?!/))', $code);
  63. }
  64. /*
  65. * Emulates tokens for newer PHP versions.
  66. */
  67. protected function emulateTokens() {
  68. // We need to manually iterate and manage a count because we'll change
  69. // the tokens array on the way
  70. $line = 1;
  71. for ($i = 0, $c = count($this->tokens); $i < $c; ++$i) {
  72. $replace = null;
  73. if (isset($this->tokens[$i + 1])) {
  74. if ($this->tokens[$i] === '?' && $this->tokens[$i + 1] === '?') {
  75. array_splice($this->tokens, $i, 2, array(
  76. array(self::T_COALESCE, '??', $line)
  77. ));
  78. $c--;
  79. continue;
  80. }
  81. if ($this->tokens[$i][0] === T_IS_SMALLER_OR_EQUAL
  82. && $this->tokens[$i + 1] === '>'
  83. ) {
  84. array_splice($this->tokens, $i, 2, array(
  85. array(self::T_SPACESHIP, '<=>', $line)
  86. ));
  87. $c--;
  88. continue;
  89. }
  90. if ($this->tokens[$i] === '*' && $this->tokens[$i + 1] === '*') {
  91. array_splice($this->tokens, $i, 2, array(
  92. array(self::T_POW, '**', $line)
  93. ));
  94. $c--;
  95. continue;
  96. }
  97. if ($this->tokens[$i] === '*' && $this->tokens[$i + 1][0] === T_MUL_EQUAL) {
  98. array_splice($this->tokens, $i, 2, array(
  99. array(self::T_POW_EQUAL, '**=', $line)
  100. ));
  101. $c--;
  102. continue;
  103. }
  104. }
  105. if (isset($this->tokens[$i + 2])) {
  106. if ($this->tokens[$i][0] === T_YIELD && $this->tokens[$i + 1][0] === T_WHITESPACE
  107. && $this->tokens[$i + 2][0] === T_STRING
  108. && !strcasecmp($this->tokens[$i + 2][1], 'from')
  109. ) {
  110. array_splice($this->tokens, $i, 3, array(
  111. array(
  112. self::T_YIELD_FROM,
  113. $this->tokens[$i][1] . $this->tokens[$i + 1][1] . $this->tokens[$i + 2][1],
  114. $line
  115. )
  116. ));
  117. $c -= 2;
  118. $line += substr_count($this->tokens[$i][1], "\n");
  119. continue;
  120. }
  121. if ($this->tokens[$i] === '.' && $this->tokens[$i + 1] === '.'
  122. && $this->tokens[$i + 2] === '.'
  123. ) {
  124. array_splice($this->tokens, $i, 3, array(
  125. array(self::T_ELLIPSIS, '...', $line)
  126. ));
  127. $c -= 2;
  128. continue;
  129. }
  130. }
  131. if (\is_array($this->tokens[$i])) {
  132. $line += substr_count($this->tokens[$i][1], "\n");
  133. }
  134. }
  135. }
  136. public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
  137. $token = parent::getNextToken($value, $startAttributes, $endAttributes);
  138. // replace new keywords by their respective tokens. This is not done
  139. // if we currently are in an object access (e.g. in $obj->namespace
  140. // "namespace" stays a T_STRING tokens and isn't converted to T_NAMESPACE)
  141. if (Tokens::T_STRING === $token && !$this->inObjectAccess) {
  142. if (isset($this->newKeywords[strtolower($value)])) {
  143. return $this->newKeywords[strtolower($value)];
  144. }
  145. } else {
  146. // keep track of whether we currently are in an object access (after ->)
  147. $this->inObjectAccess = Tokens::T_OBJECT_OPERATOR === $token;
  148. }
  149. return $token;
  150. }
  151. }