<Website>

Automap

PHK Home

File: /src/Automap/Build/Parser.php

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==-&& $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==-&& $tvalue=='"')
                
$state=self::ST_SKIPPING_BLOCK_NOSTRING;
            break;

        case 
self::ST_SKIPPING_BLOCK_NOSTRING:
            if (
$tnum==-|| $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==-&& $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==-&& $tvalue==';'$state=self::ST_OUT;
            break;
        }
    }
}

//---
// End of class
//===========================================================================
// End of class_exists
//===========================================================================
// End of namespace
//===========================================================================
?>

For more information about the PHK package format: http://phk.tekwire.net