| 
| Size: | 12958 | 
| Storage flags: | 
<?php
//=============================================================================
//
// Copyright Francois Laupretre <automap@tekwire.net>
//
//   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 <automap@tekwire.net>
* @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
// <Automap>:ignore constant T_NAMESPACE
if (!defined('T_NAMESPACE')) define('T_NAMESPACE',-2);
// <Automap>:ignore constant T_NS_SEPARATOR
if (!defined('T_NS_SEPARATOR'))    define('T_NS_SEPARATOR',-3);
// <Automap>:ignore constant T_CONST
if (!defined('T_CONST'))    define('T_CONST',-4);
// <Automap>: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 <whitespace>
const ST_NAMESPACE_2=10;            // Found 'namespace' and <whitespace>. Looking for name
const ST_CONST_FOUND=11;            // Found 'const'. Looking for name
const AUTOMAP_COMMENT=',// *<Automap>:(\S+)(.*)$,';
//---------
/** @var array(array('type' => <symbol type>,'name' => <case-sensitive symbol 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:
*    <double-slash> <Automap>:declare <type> <value>
*    <double-slash> <Automap>:ignore <type> <value>
*    <double-slash> <Automap>:ignore-file
*    <double-slash> <Automap>: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+\<Automap\>:(\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
//===========================================================================
?>