[ Index ] |
PHP Cross Reference of Automap |
[Summary view] [Print] [Text view]
1 <?php 2 //============================================================================= 3 // 4 // Copyright Francois Laupretre <automap@tekwire.net> 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 // 18 //============================================================================= 19 /** 20 * @copyright Francois Laupretre <automap@tekwire.net> 21 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0 22 * @category Automap 23 * @package Automap 24 *///========================================================================== 25 26 namespace Automap\Build { 27 28 //============================================================================= 29 //-- For PHP version < 5.3.0 30 31 // <Automap>:ignore constant T_NAMESPACE 32 if (!defined('T_NAMESPACE')) define('T_NAMESPACE',-2); 33 // <Automap>:ignore constant T_NS_SEPARATOR 34 if (!defined('T_NS_SEPARATOR')) define('T_NS_SEPARATOR',-3); 35 // <Automap>:ignore constant T_CONST 36 if (!defined('T_CONST')) define('T_CONST',-4); 37 // <Automap>:ignore constant T_TRAIT 38 if (!defined('T_TRAIT')) define('T_TRAIT',-5); 39 40 //=========================================================================== 41 /** 42 * The Automap parser 43 * 44 * This class analyzes PHP scripts, packages, or extensions to extract the 45 * symbols they define 46 * 47 * API status: Public 48 * Included in the PHK PHP runtime: No 49 * Implemented in the extension: No 50 *///========================================================================== 51 52 if (!class_exists('Automap\Build\Parser',false)) 53 { 54 class Parser implements ParserInterface 55 { 56 //-- Parser states : 57 58 const ST_OUT=1; // Upper level 59 const ST_FUNCTION_FOUND=\Automap\Mgr::T_FUNCTION; // Found 'function'. Looking for name 60 const ST_SKIPPING_BLOCK_NOSTRING=3; // In block, outside of string 61 const ST_SKIPPING_BLOCK_STRING=4; // In block, in string 62 const ST_CLASS_FOUND=\Automap\Mgr::T_CLASS; // Found 'class'. Looking for name 63 const ST_DEFINE_FOUND=6; // Found 'define'. Looking for '(' 64 const ST_DEFINE_2=7; // Found '('. Looking for constant name 65 const ST_SKIPPING_TO_EOL=8; // Got constant. Looking for EOL (';') 66 const ST_NAMESPACE_FOUND=9; // Found 'namespace'. Looking for <whitespace> 67 const ST_NAMESPACE_2=10; // Found 'namespace' and <whitespace>. Looking for name 68 const ST_CONST_FOUND=11; // Found 'const'. Looking for name 69 70 const AUTOMAP_COMMENT=',// *<Automap>:(\S+)(.*)$,'; 71 72 //--------- 73 74 /** @var array(array('type' => <symbol type>,'name' => <case-sensitive symbol name>)) */ 75 76 private $symbols; 77 78 /** @var array(symbol keys) A list of symbols to exclude */ 79 80 private $exclude_list; 81 82 //--------------------------------------------------------------------------- 83 /** 84 * Constructor 85 */ 86 87 public function __construct() 88 { 89 $this->symbols=array(); 90 $this->exclude_list=array(); 91 } 92 93 //--------------------------------- 94 95 private function cleanup() 96 { 97 // Filter out excluded symbols 98 if (count($this->exclude_list)) 99 { 100 foreach(array_keys($this->symbols) as $n) 101 { 102 $s=$this->symbols[$n]; 103 $key=\Automap\Map::key($s['type'],$s['name']); 104 if (array_search($key,$this->exclude_list)!==false) 105 unset($this->symbols[$n]); 106 } 107 } 108 109 $a=$this->symbols; 110 $this->symbols=array(); 111 $this->exclude_list=array(); 112 113 return $a; 114 } 115 116 //--------------------------------- 117 /** 118 * Mark a symbol as excluded 119 * 120 * @param string $type one of the \Automap\Mgr::T_xx constants 121 * @param string $name The symbol name 122 * @return null 123 */ 124 125 private function exclude($type,$name) 126 { 127 $this->exclude_list[]=\Automap\Map::key($type,$name); 128 } 129 130 //--------------------------------- 131 /** 132 * Add a symbol into the table 133 * 134 * Filter out the symbol from the exclude list 135 * 136 * @param string $type one of the \Automap\Mgr::T_xx constants 137 * @param string $name The symbol name 138 * @return null 139 */ 140 141 private function addSymbol($type,$name) 142 { 143 $this->symbols[]=array('type' => $type, 'name' => $name); 144 } 145 146 //--------------------------------- 147 /** 148 * Extracts symbols from an extension 149 * 150 * @param string $file Extension name 151 * @return null 152 * @throw \Exception if extension cannot be loaded 153 */ 154 155 public function parseExtension($file) 156 { 157 $extension_list=get_loaded_extensions(); 158 159 @dl($file); 160 $a=array_diff(get_loaded_extensions(),$extension_list); 161 if (($ext_name=array_pop($a))===NULL) 162 throw new \Exception($file.': Cannot load extension'); 163 164 $this->addSymbol(\Automap\Mgr::T_EXTENSION,$ext_name); 165 166 $ext=new \ReflectionExtension($ext_name); 167 168 foreach($ext->getFunctions() as $func) 169 $this->addSymbol(\Automap\Mgr::T_FUNCTION,$func->getName()); 170 171 foreach(array_keys($ext->getConstants()) as $constant) 172 $this->addSymbol(\Automap\Mgr::T_CONSTANT,$constant); 173 174 foreach($ext->getClasses() as $class) 175 $this->addSymbol(\Automap\Mgr::T_CLASS,$class->getName()); 176 177 if (method_exists($ext,'getInterfaces')) // Compatibility 178 { 179 foreach($ext->getInterfaces() as $interface) 180 $this->addSymbol(\Automap\Mgr::T_CLASS,$interface->getName()); 181 } 182 183 if (method_exists($ext,'getTraits')) // Compatibility 184 { 185 foreach($ext->getTraits() as $trait) 186 $this->addSymbol(\Automap\Mgr::T_CLASS,$trait->getName()); 187 } 188 189 return $this->cleanup(); 190 } 191 192 //--------------------------------- 193 /** 194 * Combine a namespace with a symbol 195 * 196 * The leading and trailing backslashes are first suppressed from the namespace. 197 * Then, if the namespace is not empty it is prepended to the symbol using a 198 * backslash. 199 * 200 * @param string $ns Namespace (can be empty) 201 * @param string $symbol Symbol name (cannot be empty) 202 * @return string Fully qualified name without leading backslash 203 */ 204 205 private static function combineNSSymbol($ns,$symbol) 206 { 207 $ns=trim($ns,'\\'); 208 return $ns.(($ns==='') ? '' : '\\').$symbol; 209 } 210 211 //--------------------------------- 212 /** 213 * Register explicit declarations 214 * 215 * Format: 216 * <double-slash> <Automap>:declare <type> <value> 217 * <double-slash> <Automap>:ignore <type> <value> 218 * <double-slash> <Automap>:ignore-file 219 * <double-slash> <Automap>:skip-blocks 220 * 221 * @return bool false if indexing is disabled on this file 222 */ 223 224 private function parseAutomapDirectives($buf,&$skip_blocks) 225 { 226 $a=null; 227 if (preg_match_all('{^//\s+\<Automap\>:(\S+)(.*)$}m',$buf,$a,PREG_SET_ORDER)!=0) 228 { 229 foreach($a as $match) 230 { 231 switch ($cmd=$match[1]) 232 { 233 case 'ignore-file': 234 return false; 235 236 case 'skip-blocks': 237 $skip_blocks=true; 238 break; 239 240 case 'declare': 241 case 'ignore': 242 $type_string=strtolower(strtok($match[2],' ')); 243 $name=strtok(' '); 244 if ($type_string===false || $name===false) 245 throw new \Exception($cmd.': Directive needs 2 args'); 246 $type=\Automap\Mgr::stringToType($type_string); 247 if ($cmd=='declare') 248 $this->addSymbol($type,$name); 249 else 250 $this->exclude($type,$name); 251 break; 252 253 default: 254 throw new \Exception($cmd.': Invalid Automap directive'); 255 } 256 } 257 } 258 return true; 259 } 260 261 //--------------------------------- 262 /** 263 * Extracts symbols from a PHP script file 264 * 265 * @param string $path FIle to parse 266 * @return array of symbols 267 * @throws \Exception on parse error 268 */ 269 270 public function parseScriptFile($path) 271 { 272 try 273 { 274 // Don't run PECL accelerated read for virtual files 275 $buf=((function_exists('\Automap\Ext\file_get_contents') 276 && (strpos($path,'://')===false)) ? 277 \Automap\Ext\file_get_contents($path) 278 : file_get_contents($path)); 279 $ret=$this->parseScript($buf); 280 return $ret; 281 } 282 catch (\Exception $e) 283 { throw new \Exception("$path: ".$e->getMessage()); } 284 } 285 286 //--------------------------------- 287 /** 288 * Extracts symbols from a PHP script contained in a string 289 * 290 * @param string $buf The script to parse 291 * @return array of symbols 292 * @throws \Exception on parse error 293 */ 294 295 public function parseScript($buf) 296 { 297 $buf=str_replace("\r",'',$buf); 298 299 $skip_blocks=false; 300 301 if (!$this->parseAutomapDirectives($buf,$skip_blocks)) return array(); 302 303 if (function_exists('\Automap\Ext\parseTokens')) 304 { // If PECL function is available 305 $a=\Automap\Ext\parseTokens($buf,$skip_blocks); 306 //var_dump($a);//TRACE 307 foreach($a as $k) $this->addSymbol($k{0},substr($k,1)); 308 } 309 else 310 { 311 $this->parseTokens($buf,$skip_blocks); 312 } 313 314 return $this->cleanup(); 315 } 316 317 //--------------------------------- 318 /** 319 * Extract symbols from script tokens 320 */ 321 322 private function parseTokens($buf,$skip_blocks) 323 { 324 $block_level=0; 325 $state=self::ST_OUT; 326 $name=''; 327 $ns=''; 328 329 // Note: Using php_strip_whitespace() before token_get_all does not improve 330 // performance. 331 332 foreach(token_get_all($buf) as $token) 333 { 334 if (is_string($token)) 335 { 336 $tvalue=$token; 337 $tnum=-1; 338 $tname='String'; 339 } 340 else 341 { 342 list($tnum,$tvalue)=$token; 343 $tname=token_name($tnum); 344 } 345 346 if (($tnum==T_COMMENT)||($tnum==T_DOC_COMMENT)) continue; 347 if (($tnum==T_WHITESPACE)&&($state!=self::ST_NAMESPACE_FOUND)) continue; 348 349 //echo "$tname <$tvalue>\n";//TRACE 350 switch($state) 351 { 352 case self::ST_OUT: 353 switch($tnum) 354 { 355 case T_FUNCTION: 356 $state=self::ST_FUNCTION_FOUND; 357 break; 358 case T_CLASS: 359 case T_INTERFACE: 360 case T_TRAIT: 361 $state=self::ST_CLASS_FOUND; 362 break; 363 case T_NAMESPACE: 364 $state=self::ST_NAMESPACE_FOUND; 365 $name=''; 366 break; 367 case T_CONST: 368 $state=self::ST_CONST_FOUND; 369 break; 370 case T_STRING: 371 if ($tvalue=='define') $state=self::ST_DEFINE_FOUND; 372 $name=''; 373 break; 374 // If this flag is set, we skip anything enclosed 375 // between {} chars, ignoring any conditional block. 376 case -1: 377 if ($tvalue=='{' && $skip_blocks) 378 { 379 $state=self::ST_SKIPPING_BLOCK_NOSTRING; 380 $block_level=1; 381 } 382 break; 383 } 384 break; 385 386 case self::ST_NAMESPACE_FOUND: 387 $state=($tnum==T_WHITESPACE) ? self::ST_NAMESPACE_2 : self::ST_OUT; 388 break; 389 390 case self::ST_NAMESPACE_2: 391 switch($tnum) 392 { 393 case T_STRING: 394 $name .=$tvalue; 395 break; 396 case T_NS_SEPARATOR: 397 $name .= '\\'; 398 break; 399 default: 400 $ns=$name; 401 $state=self::ST_OUT; 402 } 403 break; 404 405 406 case self::ST_FUNCTION_FOUND: 407 if (($tnum==-1)&&($tvalue=='(')) 408 { // Closure : Ignore (no function name to get here) 409 $state=self::ST_OUT; 410 break; 411 } 412 //-- Function returning ref: keep looking for name 413 if ($tnum==-1 && $tvalue=='&') break; 414 // No break here ! 415 case self::ST_CLASS_FOUND: 416 if ($tnum==T_STRING) 417 { 418 $this->addSymbol($state,self::combineNSSymbol($ns,$tvalue)); 419 } 420 else throw new \Exception('Unrecognized token for class/function definition' 421 ."(type=$tnum ($tname);value='$tvalue'). String expected"); 422 $state=self::ST_SKIPPING_BLOCK_NOSTRING; 423 $block_level=0; 424 break; 425 426 case self::ST_CONST_FOUND: 427 if ($tnum==T_STRING) 428 { 429 $this->addSymbol(\Automap\Mgr::T_CONSTANT,self::combineNSSymbol($ns,$tvalue)); 430 } 431 else throw new \Exception('Unrecognized token for constant definition' 432 ."(type=$tnum ($tname);value='$tvalue'). String expected"); 433 $state=self::ST_OUT; 434 break; 435 436 case self::ST_SKIPPING_BLOCK_STRING: 437 if ($tnum==-1 && $tvalue=='"') 438 $state=self::ST_SKIPPING_BLOCK_NOSTRING; 439 break; 440 441 case self::ST_SKIPPING_BLOCK_NOSTRING: 442 if ($tnum==-1 || $tnum==T_CURLY_OPEN) 443 { 444 switch($tvalue) 445 { 446 case '"': 447 $state=self::ST_SKIPPING_BLOCK_STRING; 448 break; 449 case '{': 450 $block_level++; 451 //TRACE echo "block_level=$block_level\n"; 452 break; 453 case '}': 454 $block_level--; 455 if ($block_level==0) $state=self::ST_OUT; 456 //TRACE echo "block_level=$block_level\n"; 457 break; 458 } 459 } 460 break; 461 462 case self::ST_DEFINE_FOUND: 463 if ($tnum==-1 && $tvalue=='(') $state=self::ST_DEFINE_2; 464 else throw new \Exception('Unrecognized token for constant definition' 465 ."(type=$tnum ($tname);value='$tvalue'). Expected '('"); 466 break; 467 468 case self::ST_DEFINE_2: 469 // Remember: T_STRING is incorrect in 'define' as constant name. 470 // Current namespace is ignored in 'define' statement. 471 if ($tnum==T_CONSTANT_ENCAPSED_STRING) 472 { 473 $schar=$tvalue{0}; 474 if ($schar=="'" || $schar=='"') $tvalue=trim($tvalue,$schar); 475 $this->addSymbol(\Automap\Mgr::T_CONSTANT,$tvalue); 476 } 477 else throw new \Exception('Unrecognized token for constant definition' 478 ."(type=$tnum ($tname);value='$tvalue'). Expected quoted string constant"); 479 $state=self::ST_SKIPPING_TO_EOL; 480 break; 481 482 case self::ST_SKIPPING_TO_EOL: 483 if ($tnum==-1 && $tvalue==';') $state=self::ST_OUT; 484 break; 485 } 486 } 487 } 488 489 //--- 490 } // End of class 491 //=========================================================================== 492 } // End of class_exists 493 //=========================================================================== 494 } // End of namespace 495 //=========================================================================== 496 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Jun 4 18:32:29 2015 | Cross-referenced by PHPXref 0.7.1 |