// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //============================================================================= /** * @copyright Francois Laupretre * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0 * @category Automap * @package Automap *///========================================================================== namespace Automap\Build { //============================================================================= //-- For PHP version < 5.3.0 // :ignore constant T_NAMESPACE if (!defined('T_NAMESPACE')) define('T_NAMESPACE',-2); // :ignore constant T_NS_SEPARATOR if (!defined('T_NS_SEPARATOR')) define('T_NS_SEPARATOR',-3); // :ignore constant T_CONST if (!defined('T_CONST')) define('T_CONST',-4); // :ignore constant T_TRAIT if (!defined('T_TRAIT')) define('T_TRAIT',-5); //=========================================================================== /** * The Automap parser * * This class analyzes PHP scripts, packages, or extensions to extract the * symbols they define * * API status: Public * Included in the PHK PHP runtime: No * Implemented in the extension: No *///========================================================================== if (!class_exists('Automap\Build\Parser',false)) { class Parser implements ParserInterface { //-- Parser states : const ST_OUT=1; // Upper level const ST_FUNCTION_FOUND=\Automap\Mgr::T_FUNCTION; // Found 'function'. Looking for name const ST_SKIPPING_BLOCK_NOSTRING=3; // In block, outside of string const ST_SKIPPING_BLOCK_STRING=4; // In block, in string const ST_CLASS_FOUND=\Automap\Mgr::T_CLASS; // Found 'class'. Looking for name const ST_DEFINE_FOUND=6; // Found 'define'. Looking for '(' const ST_DEFINE_2=7; // Found '('. Looking for constant name const ST_SKIPPING_TO_EOL=8; // Got constant. Looking for EOL (';') const ST_NAMESPACE_FOUND=9; // Found 'namespace'. Looking for const ST_NAMESPACE_2=10; // Found 'namespace' and . Looking for name const ST_CONST_FOUND=11; // Found 'const'. Looking for name const AUTOMAP_COMMENT=',// *:(\S+)(.*)$,'; //--------- /** @var array(array('type' => ,'name' => )) */ private $symbols; /** @var array(symbol keys) A list of symbols to exclude */ private $exclude_list; //--------------------------------------------------------------------------- /** * Constructor */ public function __construct() { $this->symbols=array(); $this->exclude_list=array(); } //--------------------------------- private function cleanup() { // Filter out excluded symbols if (count($this->exclude_list)) { foreach(array_keys($this->symbols) as $n) { $s=$this->symbols[$n]; $key=\Automap\Map::key($s['type'],$s['name']); if (array_search($key,$this->exclude_list)!==false) unset($this->symbols[$n]); } } $a=$this->symbols; $this->symbols=array(); $this->exclude_list=array(); return $a; } //--------------------------------- /** * Mark a symbol as excluded * * @param string $type one of the \Automap\Mgr::T_xx constants * @param string $name The symbol name * @return null */ private function exclude($type,$name) { $this->exclude_list[]=\Automap\Map::key($type,$name); } //--------------------------------- /** * Add a symbol into the table * * Filter out the symbol from the exclude list * * @param string $type one of the \Automap\Mgr::T_xx constants * @param string $name The symbol name * @return null */ private function addSymbol($type,$name) { $this->symbols[]=array('type' => $type, 'name' => $name); } //--------------------------------- /** * Extracts symbols from an extension * * @param string $file Extension name * @return null * @throw \Exception if extension cannot be loaded */ public function parseExtension($file) { $extension_list=get_loaded_extensions(); @dl($file); $a=array_diff(get_loaded_extensions(),$extension_list); if (($ext_name=array_pop($a))===NULL) throw new \Exception($file.': Cannot load extension'); $this->addSymbol(\Automap\Mgr::T_EXTENSION,$ext_name); $ext=new \ReflectionExtension($ext_name); foreach($ext->getFunctions() as $func) $this->addSymbol(\Automap\Mgr::T_FUNCTION,$func->getName()); foreach(array_keys($ext->getConstants()) as $constant) $this->addSymbol(\Automap\Mgr::T_CONSTANT,$constant); foreach($ext->getClasses() as $class) $this->addSymbol(\Automap\Mgr::T_CLASS,$class->getName()); if (method_exists($ext,'getInterfaces')) // Compatibility { foreach($ext->getInterfaces() as $interface) $this->addSymbol(\Automap\Mgr::T_CLASS,$interface->getName()); } if (method_exists($ext,'getTraits')) // Compatibility { foreach($ext->getTraits() as $trait) $this->addSymbol(\Automap\Mgr::T_CLASS,$trait->getName()); } return $this->cleanup(); } //--------------------------------- /** * Combine a namespace with a symbol * * The leading and trailing backslashes are first suppressed from the namespace. * Then, if the namespace is not empty it is prepended to the symbol using a * backslash. * * @param string $ns Namespace (can be empty) * @param string $symbol Symbol name (cannot be empty) * @return string Fully qualified name without leading backslash */ private static function combineNSSymbol($ns,$symbol) { $ns=trim($ns,'\\'); return $ns.(($ns==='') ? '' : '\\').$symbol; } //--------------------------------- /** * Register explicit declarations * * Format: * :declare * :ignore * :ignore-file * :skip-blocks * * @return bool false if indexing is disabled on this file */ private function parseAutomapDirectives($buf,&$skip_blocks) { $a=null; if (preg_match_all('{^//\s+\:(\S+)(.*)$}m',$buf,$a,PREG_SET_ORDER)!=0) { foreach($a as $match) { switch ($cmd=$match[1]) { case 'ignore-file': return false; case 'skip-blocks': $skip_blocks=true; break; case 'declare': case 'ignore': $type_string=strtolower(strtok($match[2],' ')); $name=strtok(' '); if ($type_string===false || $name===false) throw new \Exception($cmd.': Directive needs 2 args'); $type=\Automap\Mgr::stringToType($type_string); if ($cmd=='declare') $this->addSymbol($type,$name); else $this->exclude($type,$name); break; default: throw new \Exception($cmd.': Invalid Automap directive'); } } } return true; } //--------------------------------- /** * Extracts symbols from a PHP script file * * @param string $path FIle to parse * @return array of symbols * @throws \Exception on parse error */ public function parseScriptFile($path) { try { // Don't run PECL accelerated read for virtual files $buf=((function_exists('\Automap\Ext\file_get_contents') && (strpos($path,'://')===false)) ? \Automap\Ext\file_get_contents($path) : file_get_contents($path)); $ret=$this->parseScript($buf); return $ret; } catch (\Exception $e) { throw new \Exception("$path: ".$e->getMessage()); } } //--------------------------------- /** * Extracts symbols from a PHP script contained in a string * * @param string $buf The script to parse * @return array of symbols * @throws \Exception on parse error */ public function parseScript($buf) { $buf=str_replace("\r",'',$buf); $skip_blocks=false; if (!$this->parseAutomapDirectives($buf,$skip_blocks)) return array(); if (function_exists('\Automap\Ext\parseTokens')) { // If PECL function is available $a=\Automap\Ext\parseTokens($buf,$skip_blocks); //var_dump($a);//TRACE foreach($a as $k) $this->addSymbol($k{0},substr($k,1)); } else { $this->parseTokens($buf,$skip_blocks); } return $this->cleanup(); } //--------------------------------- /** * Extract symbols from script tokens */ private function parseTokens($buf,$skip_blocks) { $block_level=0; $state=self::ST_OUT; $name=''; $ns=''; // Note: Using php_strip_whitespace() before token_get_all does not improve // performance. foreach(token_get_all($buf) as $token) { if (is_string($token)) { $tvalue=$token; $tnum=-1; $tname='String'; } else { list($tnum,$tvalue)=$token; $tname=token_name($tnum); } if (($tnum==T_COMMENT)||($tnum==T_DOC_COMMENT)) continue; if (($tnum==T_WHITESPACE)&&($state!=self::ST_NAMESPACE_FOUND)) continue; //echo "$tname <$tvalue>\n";//TRACE switch($state) { case self::ST_OUT: switch($tnum) { case T_FUNCTION: $state=self::ST_FUNCTION_FOUND; break; case T_CLASS: case T_INTERFACE: case T_TRAIT: $state=self::ST_CLASS_FOUND; break; case T_NAMESPACE: $state=self::ST_NAMESPACE_FOUND; $name=''; break; case T_CONST: $state=self::ST_CONST_FOUND; break; case T_STRING: if ($tvalue=='define') $state=self::ST_DEFINE_FOUND; $name=''; break; // If this flag is set, we skip anything enclosed // between {} chars, ignoring any conditional block. case -1: if ($tvalue=='{' && $skip_blocks) { $state=self::ST_SKIPPING_BLOCK_NOSTRING; $block_level=1; } break; } break; case self::ST_NAMESPACE_FOUND: $state=($tnum==T_WHITESPACE) ? self::ST_NAMESPACE_2 : self::ST_OUT; break; case self::ST_NAMESPACE_2: switch($tnum) { case T_STRING: $name .=$tvalue; break; case T_NS_SEPARATOR: $name .= '\\'; break; default: $ns=$name; $state=self::ST_OUT; } break; case self::ST_FUNCTION_FOUND: if (($tnum==-1)&&($tvalue=='(')) { // Closure : Ignore (no function name to get here) $state=self::ST_OUT; break; } //-- Function returning ref: keep looking for name if ($tnum==-1 && $tvalue=='&') break; // No break here ! case self::ST_CLASS_FOUND: if ($tnum==T_STRING) { $this->addSymbol($state,self::combineNSSymbol($ns,$tvalue)); } else throw new \Exception('Unrecognized token for class/function definition' ."(type=$tnum ($tname);value='$tvalue'). String expected"); $state=self::ST_SKIPPING_BLOCK_NOSTRING; $block_level=0; break; case self::ST_CONST_FOUND: if ($tnum==T_STRING) { $this->addSymbol(\Automap\Mgr::T_CONSTANT,self::combineNSSymbol($ns,$tvalue)); } else throw new \Exception('Unrecognized token for constant definition' ."(type=$tnum ($tname);value='$tvalue'). String expected"); $state=self::ST_OUT; break; case self::ST_SKIPPING_BLOCK_STRING: if ($tnum==-1 && $tvalue=='"') $state=self::ST_SKIPPING_BLOCK_NOSTRING; break; case self::ST_SKIPPING_BLOCK_NOSTRING: if ($tnum==-1 || $tnum==T_CURLY_OPEN) { switch($tvalue) { case '"': $state=self::ST_SKIPPING_BLOCK_STRING; break; case '{': $block_level++; //TRACE echo "block_level=$block_level\n"; break; case '}': $block_level--; if ($block_level==0) $state=self::ST_OUT; //TRACE echo "block_level=$block_level\n"; break; } } break; case self::ST_DEFINE_FOUND: if ($tnum==-1 && $tvalue=='(') $state=self::ST_DEFINE_2; else throw new \Exception('Unrecognized token for constant definition' ."(type=$tnum ($tname);value='$tvalue'). Expected '('"); break; case self::ST_DEFINE_2: // Remember: T_STRING is incorrect in 'define' as constant name. // Current namespace is ignored in 'define' statement. if ($tnum==T_CONSTANT_ENCAPSED_STRING) { $schar=$tvalue{0}; if ($schar=="'" || $schar=='"') $tvalue=trim($tvalue,$schar); $this->addSymbol(\Automap\Mgr::T_CONSTANT,$tvalue); } else throw new \Exception('Unrecognized token for constant definition' ."(type=$tnum ($tname);value='$tvalue'). Expected quoted string constant"); $state=self::ST_SKIPPING_TO_EOL; break; case self::ST_SKIPPING_TO_EOL: if ($tnum==-1 && $tvalue==';') $state=self::ST_OUT; break; } } } //--- } // End of class //=========================================================================== } // End of class_exists //=========================================================================== } // End of namespace //=========================================================================== ?>