1
0
mirror of synced 2024-12-13 14:56:01 +03:00
doctrine2/vendor/spikephpcoverage/parser/PHPParser.php

426 lines
16 KiB
PHP

<?php
/*
* $Id: PHPParser.php 14665 2005-03-23 19:37:50Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if( ! defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/parser/Parser.php";
/**
* Parser for PHP files
*
* @author Nimish Pachapurkar (npac@spikesource.com)
* @version $Revision: 14665 $
* @package SpikePHPCoverage_Parser
*/
class PHPParser extends Parser {
/*{{{ Members */
private $inPHP = false;
private $phpStarters = array('<?php', '<?', '<?=');
private $phpFinisher = '?>';
private $inComment = false;
private $lastLineEndTokenType = "";
// If one of these tokens occur as the last token of a line
// then the next line can be treated as a continuation line
// depending on how it starts.
public static $contTypes = array(
"(",
",",
".",
"=",
T_LOGICAL_XOR,
T_LOGICAL_AND,
T_LOGICAL_OR,
T_PLUS_EQUAL,
T_MINUS_EQUAL,
T_MUL_EQUAL,
T_DIV_EQUAL,
T_CONCAT_EQUAL,
T_MOD_EQUAL,
T_AND_EQUAL,
T_OR_EQUAL,
T_XOR_EQUAL,
T_BOOLEAN_AND,
T_BOOLEAN_OR,
T_OBJECT_OPERATOR,
T_DOUBLE_ARROW,
"[",
"]",
T_LOGICAL_OR,
T_LOGICAL_XOR,
T_LOGICAL_AND
);
/*}}}*/
/*{{{ protected function processLine() */
/**
* Process a line read from the file and determine if it is an
* executable line or not.
*
* This is the work horse function that does most of the parsing.
* To parse PHP, get_all_tokens() tokenizer function is used.
*
* @param $line Line to be parsed.
* @access protected
*/
protected function processLine($line) {
// Default values
$this->lineType = LINE_TYPE_NOEXEC;
$line = trim($line);
$parseLine = $line;
$artificialStart = false;
$artificialEnd = false;
// If we are not inside PHP opening tag
if( ! $this->inPHP) {
$pos = -1;
// Confirm that the line does not have T_OPEN_TAG_WITH_ECHO (< ? =)
if(strpos($line, $this->phpStarters[2]) === false) {
// If the line has PHP start tag of the first kind
if(($pos = strpos($line, $this->phpStarters[0])) !== false) {
$pos = $pos + strlen($this->phpStarters[0]);
}
// if the line has PHP start tag of the second kind.
else if(($pos = strpos($line, $this->phpStarters[1])) !== false) {
$pos = $pos + strlen($this->phpStarters[1]);
}
// $pos now points to the character after opening tag
if($pos > 0) {
$this->inPHP = true;
//echo "Going in PHP\n";
// Remove the part of the line till the PHP opening
// tag and recurse
return $this->processLine(trim(substr($line, $pos)));
}
}
}
// If we are already in PHP
else if($this->inPHP) {
// If we are inside a multi-line comment, that is not ending
// on the same line
if((strpos($line, "/*") !== false &&
strpos($line, "*/") === false) ||
(strpos($line, "/*") > strpos($line, "*/"))) {
$this->inComment = true;
}
if($this->inComment) {
// Do we need to append an artificial comment start?
// (otherwise the tokenizer might throw error.
if(strpos($line, "/*") === false) {
$line = "/*" . $line;
$artificialStart = true;
}
// Do we need to append an artificial comment end?
if(strpos($line, "*/") === false) {
$line = $line . "*/";
$artificialEnd = true;
}
}
// Since we are inside php, append php opening and closing tags
// to prevent tokenizer from mis-interpreting the line
$parseLine = "<?php " . $line . " ?>";
}
// Tokenize
$tokens = @token_get_all($parseLine);
$this->logger->debug("inPHP? " . $this->inPHP . "\nLine:" . $parseLine,
__FILE__, __LINE__);
$this->logger->debug(print_r($tokens, true), __FILE__, __LINE__);
$seenEnough = false;
$seeMore = false;
$tokenCnt = 0; //tokens in this line
$phpEnded = false;
if($this->isContinuation($this->lastLineEndTokenType)) {
$this->lineType = LINE_TYPE_CONT;
$this->logger->debug("Continuation !", __FILE__, __LINE__);
}
foreach($tokens as $token) {
$tokenCnt ++;
if($this->inPHP) {
if($tokenCnt == 2) {
if($this->isContinuation($token)) {
$this->lineType = LINE_TYPE_CONT;
$this->logger->debug("Continuation! Token: $token",
__FILE__, __LINE__);
break;
}
}
}
if(is_string($token)) {
// FIXME: Add more cases, if needed
switch($token) {
// Any of these things, are non-executable.
case '{':
case '}':
case '(':
case ')':
case ';':
if($this->lineType != LINE_TYPE_EXEC) {
$this->lineType = LINE_TYPE_NOEXEC;
}
break;
// Everything else by default is executable.
default:
$this->lineType = LINE_TYPE_EXEC;
break;
}
$this->logger->debug("Status: " . $this->getLineTypeStr($this->lineType) . "\t\tToken: $token",
__FILE__, __LINE__);
}
else {
// The token is an array
list($tokenType, $text) = $token;
switch($tokenType) {
// If it is a comment end or start, set the correct flag
// If we have put the start or end artificially, ignore!
case T_COMMENT:
case T_DOC_COMMENT:
if(strpos($text, "/*") !== false && !$artificialStart) {
$this->inComment = true;
}
if(strpos($text, "*/") !== false && !$artificialEnd) {
$this->inComment = false;
}
case T_WHITESPACE: // white space
case T_OPEN_TAG: // < ?
case T_OPEN_TAG_WITH_ECHO: // < ? =
case T_CURLY_OPEN: //
case T_INLINE_HTML: // <br/><b>jhsk</b>
//case T_STRING: //
case T_EXTENDS: // extends
case T_STATIC: // static
case T_STRING_VARNAME: // string varname?
case T_CHARACTER: // character
case T_ELSE: // else
case T_CONSTANT_ENCAPSED_STRING: // "some str"
case T_START_HEREDOC:
// Only if decision is not already made
// mark this non-executable.
if($this->lineType != LINE_TYPE_EXEC) {
$this->lineType = LINE_TYPE_NOEXEC;
}
break;
case T_PRIVATE: // private
case T_PUBLIC: // public
case T_PROTECTED: // protected
case T_VAR: // var
case T_FUNCTION: // function
case T_CLASS: // class
case T_INTERFACE: // interface
case T_REQUIRE: // require
case T_REQUIRE_ONCE: // require_once
case T_INCLUDE: // include
case T_INCLUDE_ONCE: // include_once
case T_ARRAY: // array
case T_SWITCH: // switch
case T_CONST: // const
case T_TRY: // try
$this->lineType = LINE_TYPE_NOEXEC;
// No need to see any further
$seenEnough = true;
break;
case T_VARIABLE: // $foo
$seeMore = true;
$this->lineType = LINE_TYPE_EXEC;
break;
case T_CLOSE_TAG:
if($tokenCnt != count($tokens)) {
// Token is not last (because we inserted that)
$this->logger->debug("T_CLOSE_TAG for tokenCnt " . $tokenCnt . " End of PHP code.");
$phpEnded = true; // php end tag found within the line.
}
if($this->lineType != LINE_TYPE_EXEC) {
$this->lineType = LINE_TYPE_NOEXEC;
}
break;
default:
$seeMore = false;
$this->lineType = LINE_TYPE_EXEC;
break;
}
$this->logger->debug("Status: " . $this->getLineTypeStr($this->lineType) . "\t\tToken type: $tokenType \tText: $text",
__FILE__, __LINE__);
}
if(($this->lineType == LINE_TYPE_EXEC && !$seeMore)
|| $seenEnough) {
$this->logger->debug("Made a decision! Exiting. Token Type: $tokenType & Text: $text",
__FILE__, __LINE__);
if($seenEnough) {
$this->logger->debug("Seen enough at Token Type: $tokenType & Text: $text",
__FILE__, __LINE__);
}
break;
}
} // end foreach
$this->logger->debug("Line Type: " . $this->getLineTypeStr($this->lineType),
__FILE__, __LINE__);
if($this->inPHP) {
$this->lastLineEndTokenType = $this->getLastTokenType($tokens);
}
$this->logger->debug("Last End Token: " . $this->lastLineEndTokenType,
__FILE__, __LINE__);
if($this->inPHP) {
// Check if PHP block ends on this line
if($phpEnded) {
$this->inPHP = false;
// If line is not executable so far, check for the
// remaining part
if($this->lineType != LINE_TYPE_EXEC) {
//return $this->processLine(trim(substr($line, $pos+2)));
}
}
}
}
/*}}}*/
/*{{{ public function getLineType() */
/**
* Returns the type of line just read
*
* @return Line type
* @access public
*/
public function getLineType() {
return $this->lineType;
}
/*}}}*/
/*{{{ protected function isContinuation() */
/**
* Check if a line is a continuation of the previous line
*
* @param &$token Second token in a line (after PHP start)
* @return Boolean True if the line is a continuation; false otherwise
* @access protected
*/
protected function isContinuation(&$token) {
if(is_string($token)) {
switch($token) {
case ".":
case ",";
case "]":
case "[":
case "(":
case ")":
case "=":
return true;
}
}
else {
list($tokenType, $text) = $token;
switch($tokenType) {
case T_CONSTANT_ENCAPSED_STRING:
case T_ARRAY:
case T_DOUBLE_ARROW:
case T_OBJECT_OPERATOR:
case T_LOGICAL_XOR:
case T_LOGICAL_AND:
case T_LOGICAL_OR:
case T_PLUS_EQUAL:
case T_MINUS_EQUAL:
case T_MUL_EQUAL:
case T_DIV_EQUAL:
case T_CONCAT_EQUAL:
case T_MOD_EQUAL:
case T_AND_EQUAL:
case T_OR_EQUAL:
case T_XOR_EQUAL:
case T_BOOLEAN_AND:
case T_BOOLEAN_OR:
case T_LNUMBER:
case T_DNUMBER:
return true;
case T_STRING:
case T_VARIABLE:
return in_array($this->lastLineEndTokenType, PHPParser::$contTypes);
}
}
return false;
}
/*}}}*/
/*{{{ protected function getTokenType() */
/**
* Get the token type of a token (if exists) or
* the token itself.
*
* @param $token Token
* @return Token type or token itself
* @access protected
*/
protected function getTokenType($token) {
if(is_string($token)) {
return $token;
}
else {
list($tokenType, $text) = $token;
return $tokenType;
}
}
/*}}}*/
/*{{{*/
/**
* Return the type of last non-empty token in a line
*
* @param &$tokens Array of tokens for a line
* @return mixed Last non-empty token type (or token) if exists; false otherwise
* @access protected
*/
protected function getLastTokenType(&$tokens) {
for($i = count($tokens)-2; $i > 0; $i--) {
if(empty($tokens[$i])) {
continue;
}
if(is_string($tokens[$i])) {
return $tokens[$i];
}
else {
list($tokenType, $text) = $tokens[$i];
if($tokenType != T_WHITESPACE && $tokenType != T_EMPTY) {
return $tokenType;
}
}
}
return false;
}
/*}}}*/
/*
// Main
$obj = new PHPParser();
$obj->parse("test.php");
while(($line = $obj->getLine()) !== false) {
echo "#########################\n";
echo "[" . $line . "] Type: [" . $obj->getLineTypeStr($obj->getLineType()) . "]\n";
echo "#########################\n";
}
*/
}
?>