LimeLexer.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. /*
  3. * This file is part of the Lime test framework.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  6. * (c) Bernhard Schussek <bernhard.schussek@symfony-project.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. /**
  12. * Analyzes PHP scripts syntactically.
  13. *
  14. * You can extend this class if you want to write your own lexer that parses
  15. * a PHP file for specific information.
  16. *
  17. * To create your own lexer, implement the methods process() and getResult()
  18. * in your class. process() is called for every token in the file. You can use
  19. * the methods of this class to retrieve more information about the context of
  20. * the token, f.i. whether the token is inside a class or function etc.
  21. *
  22. * The method getResult() must return the value that should be returned by
  23. * parse().
  24. *
  25. * A lexer is stateless. This means that you can analyze any number of PHP
  26. * scripts with the same lexer instance.
  27. *
  28. * @package Lime
  29. * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  30. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  31. * @version SVN: $Id: LimeLexer.php 23701 2009-11-08 21:23:40Z bschussek $
  32. */
  33. abstract class LimeLexer
  34. {
  35. private
  36. $continue,
  37. $currentClass,
  38. $inClassDeclaration,
  39. $currentFunction,
  40. $inFunctionDeclaration,
  41. $endOfCurrentExpr,
  42. $currentLine;
  43. /**
  44. * Analyzes the given file or PHP code.
  45. *
  46. * @param string $content A file path or a string with PHP code.
  47. *
  48. * @return mixed The result from getResult()
  49. */
  50. public function parse($content)
  51. {
  52. if (is_readable($content))
  53. {
  54. $content = file_get_contents($content);
  55. }
  56. $this->continue = true;
  57. $this->currentClass = array();
  58. $this->inClassDeclaration = false;
  59. $this->currentFunction = array();
  60. $this->inFunctionDeclaration = false;
  61. $this->endOfCurrentExpr = true;
  62. $this->currentLine = 1;
  63. $tokens = token_get_all($content);
  64. $openBraces = 0;
  65. foreach ($tokens as $token)
  66. {
  67. if (is_string($token))
  68. {
  69. switch ($token)
  70. {
  71. case '{':
  72. ++$openBraces;
  73. $this->inClassDeclaration = false;
  74. $this->inFunctionDeclaration = false;
  75. break;
  76. case ';':
  77. // abstract functions
  78. if ($this->inFunctionDeclaration)
  79. {
  80. $this->inFunctionDeclaration = false;
  81. unset($this->currentFunction[$openBraces]);
  82. }
  83. $this->endOfCurrentExpr = true;
  84. break;
  85. case '}':
  86. $this->endOfCurrentExpr = true;
  87. break;
  88. }
  89. $this->beforeProcess($token, null);
  90. $this->process($token, null);
  91. $this->afterProcess($token, null);
  92. switch ($token)
  93. {
  94. case '}':
  95. --$openBraces;
  96. if (array_key_exists($openBraces, $this->currentClass))
  97. {
  98. unset($this->currentClass[$openBraces]);
  99. }
  100. if (array_key_exists($openBraces, $this->currentFunction))
  101. {
  102. unset($this->currentFunction[$openBraces]);
  103. }
  104. break;
  105. }
  106. }
  107. else
  108. {
  109. list($id, $text) = $token;
  110. switch ($id)
  111. {
  112. case T_CURLY_OPEN:
  113. case T_DOLLAR_OPEN_CURLY_BRACES:
  114. ++$openBraces;
  115. break;
  116. case T_OPEN_TAG:
  117. case T_CLOSE_TAG:
  118. $this->endOfCurrentExpr = true;
  119. $this->currentLine += count(explode("\n", $text)) - 1;
  120. break;
  121. case T_WHITESPACE:
  122. case T_START_HEREDOC:
  123. case T_CONSTANT_ENCAPSED_STRING:
  124. case T_ENCAPSED_AND_WHITESPACE:
  125. case T_COMMENT:
  126. case T_DOC_COMMENT:
  127. $this->currentLine += count(explode("\n", $text)) - 1;
  128. break;
  129. case T_ABSTRACT:
  130. if ($this->inClass())
  131. {
  132. $this->currentFunction[$openBraces] = null;
  133. $this->inFunctionDeclaration = true;
  134. }
  135. else
  136. {
  137. $this->currentClass[$openBraces] = null;
  138. $this->inClassDeclaration = true;
  139. }
  140. break;
  141. case T_INTERFACE:
  142. case T_CLASS:
  143. $this->currentClass[$openBraces] = null;
  144. $this->inClassDeclaration = true;
  145. break;
  146. case T_FUNCTION:
  147. $this->currentFunction[$openBraces] = null;
  148. $this->inFunctionDeclaration = true;
  149. break;
  150. case T_STRING:
  151. if (array_key_exists($openBraces, $this->currentClass) && is_null($this->currentClass[$openBraces]))
  152. {
  153. $this->currentClass[$openBraces] = $text;
  154. }
  155. if (array_key_exists($openBraces, $this->currentFunction) && is_null($this->currentFunction[$openBraces]))
  156. {
  157. $this->currentFunction[$openBraces] = $text;
  158. }
  159. break;
  160. case T_AND_EQUAL:
  161. case T_BREAK:
  162. case T_CASE:
  163. case T_CATCH:
  164. case T_CLONE:
  165. case T_CONCAT_EQUAL:
  166. case T_CONTINUE:
  167. case T_DEC:
  168. case T_DECLARE:
  169. case T_DEFAULT:
  170. case T_DIV_EQUAL:
  171. case T_DO:
  172. case T_ECHO:
  173. case T_ELSEIF:
  174. case T_EMPTY:
  175. case T_ENDDECLARE:
  176. case T_ENDFOR:
  177. case T_ENDFOREACH:
  178. case T_ENDIF:
  179. case T_ENDSWITCH:
  180. case T_ENDWHILE:
  181. case T_END_HEREDOC:
  182. case T_EVAL:
  183. case T_EXIT:
  184. case T_FOR:
  185. case T_FOREACH:
  186. case T_GLOBAL:
  187. case T_IF:
  188. case T_INC:
  189. case T_INCLUDE:
  190. case T_INCLUDE_ONCE:
  191. case T_INSTANCEOF:
  192. case T_ISSET:
  193. case T_IS_EQUAL:
  194. case T_IS_GREATER_OR_EQUAL:
  195. case T_IS_IDENTICAL:
  196. case T_IS_NOT_EQUAL:
  197. case T_IS_NOT_IDENTICAL:
  198. case T_IS_SMALLER_OR_EQUAL:
  199. case T_LIST:
  200. case T_LOGICAL_AND:
  201. case T_LOGICAL_OR:
  202. case T_LOGICAL_XOR:
  203. case T_MINUS_EQUAL:
  204. case T_MOD_EQUAL:
  205. case T_MUL_EQUAL:
  206. case T_NEW:
  207. case T_OBJECT_OPERATOR:
  208. case T_OR_EQUAL:
  209. case T_PLUS_EQUAL:
  210. case T_PRINT:
  211. case T_REQUIRE:
  212. case T_REQUIRE_ONCE:
  213. case T_RETURN:
  214. case T_SL:
  215. case T_SL_EQUAL:
  216. case T_SR:
  217. case T_SR_EQUAL:
  218. case T_SWITCH:
  219. case T_THROW:
  220. case T_TRY:
  221. case T_UNSET:
  222. case T_UNSET_CAST:
  223. case T_USE:
  224. case T_WHILE:
  225. case T_XOR_EQUAL:
  226. $this->endOfCurrentExpr = false;
  227. break;
  228. }
  229. $this->beforeProcess($text, $id);
  230. $this->process($text, $id);
  231. $this->afterProcess($text, $id);
  232. }
  233. if (!$this->continue)
  234. {
  235. break;
  236. }
  237. }
  238. return $this->getResult();
  239. }
  240. protected function beforeProcess($text, $id)
  241. {
  242. }
  243. protected function afterProcess($text, $id)
  244. {
  245. }
  246. /**
  247. * Processes a token in the PHP code.
  248. *
  249. * @param string $text The string representation of the token
  250. * @param integer $id The token identifier (f.i. T_VARIABLE) or NULL, if
  251. * the token does not have an identifier.
  252. */
  253. abstract protected function process($text, $id);
  254. /**
  255. * Returns the result of the lexing process.
  256. *
  257. * @return mixed
  258. */
  259. abstract protected function getResult();
  260. /**
  261. * Returns the line number at the current position of the lexer.
  262. *
  263. * @return integer
  264. */
  265. protected function getCurrentLine()
  266. {
  267. return $this->currentLine;
  268. }
  269. /**
  270. * Returns the class name at the current position of the lexer.
  271. *
  272. * @return string Returns NULL if the current position is not inside a class.
  273. */
  274. protected function getCurrentClass()
  275. {
  276. return $this->inClass() ? end($this->currentClass) : null;
  277. }
  278. /**
  279. * Returns the function name at the current position of the lexer.
  280. *
  281. * @return string Returns NULL if the current position is not inside a function.
  282. */
  283. protected function getCurrentFunction()
  284. {
  285. return $this->inFunction() ? end($this->currentFunction) : null;
  286. }
  287. /**
  288. * Returns whether the current position of the lexer is inside a class.
  289. *
  290. * @return boolean
  291. */
  292. protected function inClass()
  293. {
  294. return count($this->currentClass) > 0;
  295. }
  296. /**
  297. * Returns whether the current position of the lexer is inside a class
  298. * declaration (f.i. "abstract class ClassName extends BaseClass").
  299. *
  300. * @return boolean
  301. */
  302. protected function inClassDeclaration()
  303. {
  304. return $this->inClassDeclaration;
  305. }
  306. /**
  307. * Returns whether the current position of the lexer is inside a function.
  308. *
  309. * @return boolean
  310. */
  311. protected function inFunction()
  312. {
  313. return count($this->currentFunction) > 0;
  314. }
  315. /**
  316. * Returns whether the current position of the lexer is inside a function
  317. * declaration (f.i. "protected function myFunctionName()").
  318. *
  319. * @return boolean
  320. */
  321. protected function inFunctionDeclaration()
  322. {
  323. return $this->inFunctionDeclaration;
  324. }
  325. /**
  326. * Returns whether the current token marks the end of the last expression.
  327. *
  328. * @return boolean
  329. */
  330. protected function isEndOfCurrentExpr()
  331. {
  332. return $this->endOfCurrentExpr;
  333. }
  334. /**
  335. * Tells the lexer to stop lexing.
  336. */
  337. protected function stop()
  338. {
  339. $this->continue = false;
  340. }
  341. }