[ Index ]

PHP Cross Reference of Automap

title

Body

[close]

/Automap/Build/ -> Parser.php (source)

   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  ?>


Generated: Thu Jun 4 18:32:29 2015 Cross-referenced by PHPXref 0.7.1