/src/phpMorphy/Generator/PhpFileParser.php

https://github.com/heromantor/phpmorphy · PHP · 311 lines · 200 code · 44 blank · 67 comment · 17 complexity · e3fbbcd5035e23a84ab97e267263a7ac MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of phpMorphy project
  4. *
  5. * Copyright (c) 2007-2012 Kamaev Vladimir <heromantor@users.sourceforge.net>
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the
  19. * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  20. * Boston, MA 02111-1307, USA.
  21. */
  22. class phpMorphy_Generator_PhpFileParser {
  23. /** @var string */
  24. private $namespace;
  25. /** @var array */
  26. private $tokens;
  27. /** @var int */
  28. private $token_pos;
  29. /** @var int */
  30. private $offset;
  31. /** @var int */
  32. private $line;
  33. private function __construct() {
  34. }
  35. /**
  36. * @static
  37. * @param string $fileName
  38. * @return phpMorphy_Generator_PhpFileParser_FileDescriptor
  39. */
  40. static function parseFile($fileName) {
  41. return self::parseString(file_get_contents($fileName));
  42. }
  43. /**
  44. * @static
  45. * @param string $string
  46. * @return phpMorphy_Generator_PhpFileParser_FileDescriptor
  47. */
  48. static function parseString($string) {
  49. return self::parseArray(token_get_all($string));
  50. }
  51. /**
  52. * @static
  53. * @param array $tokens
  54. * @return phpMorphy_Generator_PhpFileParser_FileDescriptor
  55. */
  56. static function parseArray(array $tokens) {
  57. $that = new phpMorphy_Generator_PhpFileParser;
  58. $that->tokens = $tokens;
  59. $file_descriptor = new phpMorphy_Generator_PhpFileParser_FileDescriptor();
  60. $that->doParse($file_descriptor);
  61. $file_descriptor->finalize();
  62. return $file_descriptor;
  63. }
  64. private function tokenName($token) {
  65. return is_string($token) ? $token : token_name($token[0]);
  66. }
  67. /**
  68. * Parses phpMorphy_Generator_PhpFileParser_phpDoc for namespace or class|interface
  69. * @param phpMorphy_Generator_PhpFileParser_phpDoc $doc
  70. * @return array|phpMorphy_Generator_PhpFileParser_ClassDescriptor|string
  71. */
  72. private function parsePhpDoc(phpMorphy_Generator_PhpFileParser_phpDoc $doc) {
  73. $start_token = $this->tokens[$this->token_pos];
  74. $tokens = $this->tokens;
  75. for($this->token_pos++, $c = count($tokens); $this->token_pos < $c; $this->token_pos++) {
  76. $token = $tokens[$this->token_pos];
  77. switch($token[0]) {
  78. case T_NAMESPACE:
  79. $this->parseNamespace($doc);
  80. return array();
  81. case T_ABSTRACT:
  82. case T_FINAL:
  83. case T_CLASS:
  84. case T_INTERFACE:
  85. return array($this->parseClass($doc));
  86. case T_COMMENT:
  87. case T_WHITESPACE:
  88. break;
  89. default:
  90. return array();
  91. }
  92. }
  93. }
  94. /**
  95. * Parses namespace definition
  96. * @throws phpMorphy_Generator_PhpFileParser_Exception
  97. * @param null|phpMorphy_Generator_PhpFileParser_phpDoc $doc
  98. * @return string
  99. */
  100. private function parseNamespace(phpMorphy_Generator_PhpFileParser_phpDoc $doc = null) {
  101. $start_token = $this->tokens[$this->token_pos];
  102. $tokens = $this->tokens;
  103. for($this->token_pos++, $c = count($tokens); $this->token_pos < $c; $this->token_pos++) {
  104. $token = $tokens[$this->token_pos];
  105. $lexem = is_string($token) ? $token : $token[0];
  106. switch($lexem) {
  107. case T_STRING:
  108. $this->namespace = $token[1];
  109. return $token[1];
  110. case T_WHITESPACE:
  111. case T_COMMENT:
  112. case T_DOC_COMMENT:
  113. break;
  114. default:
  115. throw new phpMorphy_Generator_PhpFileParser_Exception("Unexpected token '" . $this->tokenName($token) . "' in namespace definition", $start_token);
  116. }
  117. }
  118. throw new phpMorphy_Generator_PhpFileParser_Exception("Unexpected end of file in namespace definition", $start_token);
  119. }
  120. /**
  121. * Parses class definition
  122. * @throws phpMorphy_Generator_PhpFileParser_Exception
  123. * @param null|phpMorphy_Generator_PhpFileParser_phpDoc $doc
  124. * @param null|string $namespace
  125. * @return phpMorphy_Generator_PhpFileParser_ClassDescriptor
  126. */
  127. private function parseClass(phpMorphy_Generator_PhpFileParser_phpDoc $doc = null) {
  128. $start_token = $this->tokens[$this->token_pos];
  129. $tokens = $this->tokens;
  130. $result = new phpMorphy_Generator_PhpFileParser_ClassDescriptor();
  131. $result->startLine = $start_token[2];
  132. $result->phpDoc = $doc;
  133. $result->namespace = $this->namespace;
  134. for($c = count($tokens); $this->token_pos < $c; $this->token_pos++) {
  135. $token = $tokens[$this->token_pos];
  136. $lexem = is_string($token) ? $token : $token[0];
  137. switch($lexem) {
  138. case T_CLASS:
  139. $result->type = phpMorphy_Generator_PhpFileParser_ClassDescriptor::IS_CLASS;
  140. break;
  141. case T_INTERFACE:
  142. $result->type = phpMorphy_Generator_PhpFileParser_ClassDescriptor::IS_INTERFACE;
  143. break;
  144. case T_STRING:
  145. if(null === $result->type) {
  146. throw new phpMorphy_Generator_PhpFileParser_Exception("Unexpected token '" . $this->tokenName($token) . "' in class definition", $start_token);
  147. }
  148. if(null === $result->name) {
  149. $result->name = $token[1];
  150. }
  151. break;
  152. case T_CURLY_OPEN: // ???
  153. case '{':
  154. $this->parseOpenCurlyBracket();
  155. $this->token_pos++;
  156. if(false === ($end_line = $this->findFirstTokenLine(1))) {
  157. $this->token_pos--;
  158. if(false === ($end_line = $this->findFirstTokenLine(-1))) {
  159. throw new phpMorphy_Generator_PhpFileParser_Exception("Can`t find end line for class", $start_token);
  160. }
  161. }
  162. $result->endLine = $end_line;
  163. return $result;
  164. case T_DOC_COMMENT:
  165. case T_COMMENT:
  166. case T_WHITESPACE:
  167. case T_ABSTRACT:
  168. case T_FINAL:
  169. case T_EXTENDS:
  170. case T_IMPLEMENTS:
  171. case ',':
  172. break;
  173. default:
  174. throw new phpMorphy_Generator_PhpFileParser_Exception("Unexpected token '" . $this->tokenName($token) . "' in class definition", $start_token);
  175. }
  176. }
  177. throw new phpMorphy_Generator_PhpFileParser_Exception("Unexpected end of file in class definition", $start_token);
  178. }
  179. /**
  180. * @param int $direction
  181. * @return int|false
  182. */
  183. private function findFirstTokenLine($direction) {
  184. $pos = $this->token_pos;
  185. for($c = count($this->tokens); $pos < $c && $pos >= 0; $pos += $direction) {
  186. if(is_array($this->tokens[$pos])) {
  187. $token = $this->tokens[$pos];
  188. return $direction > 0 ? $token[2] : $token[2] + substr_count($token[1], "\n");
  189. }
  190. }
  191. return false;
  192. }
  193. /**
  194. * Seeks to pair close curly bracket
  195. * @throws phpMorphy_Generator_PhpFileParser_Exception
  196. * @return void
  197. */
  198. private function parseOpenCurlyBracket() {
  199. $opens = 1;
  200. $start_token = $this->tokens[$this->token_pos];
  201. $tokens = $this->tokens;
  202. for($this->token_pos++, $c = count($tokens); $this->token_pos < $c; $this->token_pos++) {
  203. $token = $tokens[$this->token_pos];
  204. $lexem = is_string($token) ? $token : $token[0];
  205. switch($lexem) {
  206. case T_CURLY_OPEN:
  207. case '{':
  208. $opens++;
  209. break;
  210. case '}':
  211. $opens--;
  212. if(0 === $opens) {
  213. return;
  214. }
  215. break;
  216. }
  217. }
  218. throw new phpMorphy_Generator_PhpFileParser_Exception("Unexpected end of file, expect close curly bracket", $start_token);
  219. }
  220. private function doParse(phpMorphy_Generator_PhpFileParser_FileDescriptor $file_descriptor) {
  221. $processed = 0;
  222. $tokens = $this->tokens;
  223. $this->token_pos = 0;
  224. $this->offset = 0;
  225. $this->line = 1;
  226. for($c = count($tokens); $this->token_pos < $c; $this->token_pos++) {
  227. $token = $tokens[$this->token_pos];
  228. $lexem = is_string($token) ? $token : $token[0];
  229. switch($lexem) {
  230. case T_DOC_COMMENT:
  231. if($processed === 0) {
  232. $pos = $this->token_pos + 1;
  233. if($pos < $c) {
  234. $next_token = $tokens[$pos];
  235. if(is_array($next_token) &&
  236. T_WHITESPACE === $next_token[0] &&
  237. substr_count($next_token[1], "\n") > 1
  238. ) {
  239. $file_descriptor->phpDoc = new phpMorphy_Generator_PhpFileParser_phpDoc($token);
  240. break;
  241. }
  242. }
  243. }
  244. $file_descriptor->classes = array_merge(
  245. $file_descriptor->classes,
  246. $this->parsePhpDoc(new phpMorphy_Generator_PhpFileParser_phpDoc($token))
  247. );
  248. break;
  249. case T_NAMESPACE:
  250. $this->parseNamespace();
  251. break;
  252. case T_CLASS:
  253. case T_INTERFACE:
  254. case T_ABSTRACT:
  255. case T_FINAL:
  256. $file_descriptor->classes[] = $this->parseClass();
  257. break;
  258. case T_WHITESPACE:
  259. case T_COMMENT:
  260. case T_OPEN_TAG:
  261. $processed--;
  262. break;
  263. }
  264. $processed++;
  265. }
  266. }
  267. }