#include <StringConstants.au3> ; to declare the Constants of StringRegExp
#include <Array.au3> ; UDF needed for _ArrayDisplay and _ArrayConcatenate
Local $sRegex = "(?ixms)(\s+)(?:abstract\s+|final\s+|private\s+|protected\s+|public\s+)?(?:static\s+)?(function)\s+(\w+)\s*(\((?>[^()]+|(?R))*\))|({(?>[^{}]+|(?R))*})"
Local $sString = "<?php declare(strict_types=1);" & @CRLF & _
"" & @CRLF & _
"namespace PHPAstVisualizer;" & @CRLF & _
"" & @CRLF & _
"use phpDocumentor\GraphViz\Edge as GraphEdge;" & @CRLF & _
"use phpDocumentor\GraphViz\Graph;" & @CRLF & _
"use phpDocumentor\GraphViz\Node as GraphNode;" & @CRLF & _
"" & @CRLF & _
"class Options {" & @CRLF & _
" private $options = [" & @CRLF & _
" 'graph' => []," & @CRLF & _
" 'node' => ['shape' => 'rect']," & @CRLF & _
" 'edge' => []," & @CRLF & _
" 'childEdge' => ['style' => 'dashed','arrowhead' => 'empty']," & @CRLF & _
" ];" & @CRLF & _
" private $name;" & @CRLF & _
"}" & @CRLF & _
"" & @CRLF & _
" public function __construct(string $name, array $options = []) {" & @CRLF & _
" $this->name = $name;" & @CRLF & _
" $this->graphOptions = array_merge($this->options, $options);" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function getName(): string {" & @CRLF & _
" return $this->name;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function graph(Graph $graph) {" & @CRLF & _
" foreach ($this->options['graph'] as $name => $value) {" & @CRLF & _
" $graph->{'set' . $name}($value);" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function node(GraphNode $node) {" & @CRLF & _
" foreach ($this->options['node'] as $name => $value) {" & @CRLF & _
" $node->{'set' . $name}($value);" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function childEdge(GraphEdge $edge) {" & @CRLF & _
" $this->edge($edge);" & @CRLF & _
" foreach ($this->options['childEdge'] as $name => $value) {" & @CRLF & _
" $edge->{'set' . $name}($value);" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function edge(GraphEdge $edge) {" & @CRLF & _
" foreach ($this->options['edge'] as $name => $value) {" & @CRLF & _
" $edge->{'set' . $name}($value);" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"}" & @CRLF & _
" /**" & @CRLF & _
" * PHP file with an autoload function which will be included into the" & @CRLF & _
" * sandbox of the InstantSVC CodeAnalyzer" & @CRLF & _
" * @var string" & @CRLF & _
" */" & @CRLF & _
" protected $autoloadFile = '';" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * Sets the output driver and initializes" & @CRLF & _
" * @param CallgraphDriver $driver output driver to use" & @CRLF & _
" * @return PHPCallGraph" & @CRLF & _
" */" & @CRLF & _
" public function __construct(CallgraphDriver $driver = null) {" & @CRLF & _
" if ($driver != null) {" & @CRLF & _
" $this->driver = $driver;" & @CRLF & _
" } else {" & @CRLF & _
" $this->driver = new TextDriver();" & @CRLF & _
" }" & @CRLF & _
" $functions = get_defined_functions();" & @CRLF & _
" $this->internalFunctions = $functions['internal'];" & @CRLF & _
" // List of PHP keywords which could be followed by an opening parenthis" & @CRLF & _
" // taken from PHP Manual (http://www.php.net/reserved_keywords)" & @CRLF & _
" $this->internalKeywords = array(" & @CRLF & _
" 'array', 'declare', 'die', 'echo', 'elseif', 'empty', 'eval'," & @CRLF & _
" 'exit', 'for', 'foreach', 'global', 'if', 'include', 'include_once'," & @CRLF & _
" 'isset', 'list', 'print', 'require', 'require_once', 'return'," & @CRLF & _
" 'switch', 'unset', 'while', 'catch', 'or', 'and', 'xor', 'new Exception');" & @CRLF & _
" $this->constants = array_keys(get_defined_constants());" & @CRLF & _
"" & @CRLF & _
" //TODO: provide setter for this" & @CRLF & _
" //$this->ignoreList = array('PHPCallGraph::setDriver', 'PHPCallGraph::__construct', 'PHPCallGraph::setShowExternalCalls', 'PHPCallGraph::setShowInternalFunctions', 'PHPCallGraph::save', 'PHPCallGraph::__toString');" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function setDriver(CallgraphDriver $driver = null) {" & @CRLF & _
" if ($driver != null) {" & @CRLF & _
" $this->driver = $driver;" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * Enable or disable printing of debug information" & @CRLF & _
" * @param boolean $enabled" & @CRLF & _
" */" & @CRLF & _
" public function setDebug($enabled = true) {" & @CRLF & _
" $this->debug = $enabled;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" protected function debug($string) {" & @CRLF & _
" if ($this->debug) {" & @CRLF & _
" print "||PHPCallGraph| ".$string."\n";" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" protected function info($string) {" & @CRLF & _
" if ($this->showInfo) {" & @CRLF & _
" print "||PHPCallGraph| ".$string."\n";" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" protected function warning($string) {" & @CRLF & _
" if ($this->showWarnings) {" & @CRLF & _
" print "||PHPCallGraph* *WARNING* ".$string."\n";" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * Sets a PHP file with an autoload function which will be included into" & @CRLF & _
" * the sandbox of the InstantSVC CodeAnalyzer" & @CRLF & _
" * @param string $filename Name of a PHP file with an autoload function" & @CRLF & _
" * @return boolean success" & @CRLF & _
" */" & @CRLF & _
" public function setAutoloadFile($filename) {" & @CRLF & _
" $returnValue = false;" & @CRLF & _
" if (!empty($filename) and is_file($filename) and is_readable($filename)) {" & @CRLF & _
" $this->autoloadFile = $filename;" & @CRLF & _
" $returnValue = true;" & @CRLF & _
" } else {" & @CRLF & _
" //TODO: throw exception" & @CRLF & _
" }" & @CRLF & _
" return $returnValue;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function setShowExternalCalls($boolean = true) {" & @CRLF & _
" $this->showExternalCalls = $boolean;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function setShowInternalFunctions($boolean = true) {" & @CRLF & _
" $this->showInternalFunctions = $boolean;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function collectFileNames(array $filesOrDirs, $recursive = false) {" & @CRLF & _
" $files = array();" & @CRLF & _
" foreach ($filesOrDirs as $fileOrDir) {" & @CRLF & _
" if (is_file($fileOrDir)) {" & @CRLF & _
" $files[] = $fileOrDir;" & @CRLF & _
" } elseif (is_dir($fileOrDir)) {" & @CRLF & _
" $globbed = glob("$fileOrDir/*");" & @CRLF & _
" if ($recursive) {" & @CRLF & _
" $files = array_merge($files, $this->collectFileNames($globbed, true));" & @CRLF & _
" } else {" & @CRLF & _
" foreach($globbed as $path) {" & @CRLF & _
" if (is_file($path)) {" & @CRLF & _
" $files[] = $path;" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" return $files;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function parse(array $filesOrDirs, $recursive = false) {" & @CRLF & _
" $files = $this->collectFileNames($filesOrDirs, $recursive);" & @CRLF & _
" if ($this->debug) {" & @CRLF & _
" var_dump($files);" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" $ca = new iscCodeAnalyzer(null);" & @CRLF & _
" $ca->setDebug($this->debug);" & @CRLF & _
" $ca->setAutoloadFile($this->autoloadFile);" & @CRLF & _
" $ca->inspectFiles($files);" & @CRLF & _
" $this->codeSummary = $ca->getCodeSummary();" & @CRLF & _
" $this->analyseCodeSummary();" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function parseFile($file) {" & @CRLF & _
" $ca = new iscCodeAnalyzer(null);" & @CRLF & _
" $ca->setDebug($this->debug);" & @CRLF & _
" $ca->setAutoloadFile($this->autoloadFile);" & @CRLF & _
" $ca->inspectFiles(array($file));" & @CRLF & _
" $this->codeSummary = $ca->getCodeSummary();" & @CRLF & _
" $this->analyseCodeSummary();" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function parseDir($dir = '.') {" & @CRLF & _
" $ca = new iscCodeAnalyzer($dir);" & @CRLF & _
" $ca->setDebug($this->debug);" & @CRLF & _
" $ca->setAutoloadFile($this->autoloadFile);" & @CRLF & _
" $ca->collect();" & @CRLF & _
" $this->codeSummary = $ca->getCodeSummary();" & @CRLF & _
" $this->analyseCodeSummary();" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function analyseCodeSummary() {" & @CRLF & _
" $this->buildLookupTables();" & @CRLF & _
"" & @CRLF & _
" //TODO: analyze code in the global scope" & @CRLF & _
" // currently a workarround is to manually wrap such code" & @CRLF & _
" // in a dummy function called dummyFunctionForFile_filename_php()" & @CRLF & _
"" & @CRLF & _
" // analyze functions" & @CRLF & _
" if (!empty($this->codeSummary['functions'])) {" & @CRLF & _
" foreach ($this->codeSummary['functions'] as $functionName => $function) {" & @CRLF & _
" $this->parseMethodBody(" & @CRLF & _
" '-'," & @CRLF & _
" $functionName," & @CRLF & _
" array()," & @CRLF & _
" array()," & @CRLF & _
" $function['file']," & @CRLF & _
" $function['startLine']," & @CRLF & _
" $function['endLine']" & @CRLF & _
" );" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" // analyze classes" & @CRLF & _
" if (!empty($this->codeSummary['classes'])) {" & @CRLF & _
" foreach ($this->codeSummary['classes'] as $className => $class) {" & @CRLF & _
" /*" & @CRLF & _
" echo $className, "\n";" & @CRLF & _
" var_export($class);" & @CRLF & _
" echo "\n\n";" & @CRLF & _
" //*/" & @CRLF & _
" if (!empty($class['methods'])) {" & @CRLF & _
" $propertyNames = array_keys($class['properties']);" & @CRLF & _
" $methodNames = array_keys($class['methods']);" & @CRLF & _
" //var_export($propertyNames);" & @CRLF & _
" //var_export($methodNames);" & @CRLF & _
" foreach ($class['methods'] as $methodName => $method) {" & @CRLF & _
" $this->parseMethodBody(" & @CRLF & _
" $className," & @CRLF & _
" $methodName," & @CRLF & _
" $propertyNames," & @CRLF & _
" $methodNames," & @CRLF & _
" $class['file']," & @CRLF & _
" $method['startLine']," & @CRLF & _
" $method['endLine']" & @CRLF & _
" );" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" protected function buildLookupTables() {" & @CRLF & _
" if (!empty($this->codeSummary['classes'])) {" & @CRLF & _
" foreach ($this->codeSummary['classes'] as $className => $class) {" & @CRLF & _
" //currently unused" & @CRLF & _
" /*" & @CRLF & _
" if (!empty($class['properties'])) {" & @CRLF & _
" foreach ($class['properties'] as $propertyName => $property) {" & @CRLF & _
" $this->propertyLookupTable[$propertyName][] = $className;" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" //*/" & @CRLF & _
" if (!empty($class['methods'])) {" & @CRLF & _
" foreach ($class['methods'] as $methodName => $method) {" & @CRLF & _
" $this->methodLookupTable[$methodName][] = $className;" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * @param string $className" & @CRLF & _
" * @param string $methodName" & @CRLF & _
" * @param array $propertyNames" & @CRLF & _
" * @param array $methodNames" & @CRLF & _
" * @param string $file" & @CRLF & _
" * @param integer $startLine" & @CRLF & _
" * @param integer $endLine" & @CRLF & _
" */" & @CRLF & _
" public function parseMethodBody(" & @CRLF & _
" $className," & @CRLF & _
" $methodName," & @CRLF & _
" Array $propertyNames," & @CRLF & _
" Array $methodNames," & @CRLF & _
" $file," & @CRLF & _
" $startLine," & @CRLF & _
" $endLine" & @CRLF & _
" ) {" & @CRLF & _
" if ($className == '-') { // we are analyzing a function not a method" & @CRLF & _
" if (substr($methodName, 0, strlen('dummyFunctionForFile_')) == 'dummyFunctionForFile_') {" & @CRLF & _
" // the function has been introduced manually to encapsulate code in the global scope" & @CRLF & _
" $callerName = str_replace('_', '.', substr($methodName, strlen('dummyFunctionForFile_'))) . '(File)';" & @CRLF & _
" } else {" & @CRLF & _
" $callerName = $methodName . $this->generateParametersForSignature($this->codeSummary['functions'][$methodName]['params']);" & @CRLF & _
" }" & @CRLF & _
" } else {" & @CRLF & _
" //TODO: visibilities" & @CRLF & _
" $callerName = $className . '::' . $methodName . $this->generateParametersForSignature($this->codeSummary['classes'][$className]['methods'][$methodName]['params']);" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" $callerNameWithoutParameterList = substr($callerName, 0, strpos($callerName, '('));" & @CRLF & _
" " & @CRLF & _
" if (!in_array($callerNameWithoutParameterList, $this->ignoreList)) {" & @CRLF & _
" $this->debug('phpCallGraph: analyzing ' . $callerName);" & @CRLF & _
" $offset = $startLine - 1;" & @CRLF & _
" $length = $endLine - $startLine + 1;" & @CRLF & _
"" & @CRLF & _
" // obtain source code" & @CRLF & _
" $memberCode = implode('', array_slice(file($file), $offset, $length));" & @CRLF & _
" $memberCode = "<?php\nclass $className {\n" . $memberCode . "}\n?>\n";" & @CRLF & _
" //echo $memberCode;" & @CRLF & _
"" & @CRLF & _
" $this->debug("= Analyzing $callerName =");" & @CRLF & _
" $this->info(" defined in $file on line $offset");" & @CRLF & _
" $this->driver->startFunction($offset, $file, $callerName, $memberCode);" & @CRLF & _
"" & @CRLF & _
" $insideDoubleQuotedString = false;" & @CRLF & _
" $lineNumber = $offset - 1;" & @CRLF & _
" $blocksStarted = 0;" & @CRLF & _
" // parse source code" & @CRLF & _
" $tokens = token_get_all($memberCode);" & @CRLF & _
" /*" & @CRLF & _
" if ($methodName == '__construct') {" & @CRLF & _
" print_r($tokens);" & @CRLF & _
" }" & @CRLF & _
" //*/" & @CRLF & _
"" & @CRLF & _
" //TODO: implement a higher level API for working with PHP parser tokens (e.g. TokenIterator)" & @CRLF & _
" foreach ($tokens as $i => $token) {" & @CRLF & _
" //TODO: obtain method signature directly from the source file" & @CRLF & _
" if (is_array($token)) {" & @CRLF & _
" $lineNumber+= substr_count($token[1], "\n");" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /*" & @CRLF & _
" if (count($token) == 3) {" & @CRLF & _
" echo "\t", token_name($token[0]), "\n";" & @CRLF & _
" echo "\t\t", $token[1], "\n";" & @CRLF & _
" } else {" & @CRLF & _
" echo "\t", $token[0], "\n";" & @CRLF & _
" }" & @CRLF & _
" //*/" & @CRLF & _
"" & @CRLF & _
" // skip call analysis for the method signature" & @CRLF & _
" if ($blocksStarted < 2) {" & @CRLF & _
" // method body not yet started" & @CRLF & _
" if ($token[0] == '{') {" & @CRLF & _
" ++$blocksStarted;" & @CRLF & _
" }" & @CRLF & _
" continue;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" if (!$insideDoubleQuotedString and $token == '"') {" & @CRLF & _
" $insideDoubleQuotedString = true;" & @CRLF & _
" } elseif ($insideDoubleQuotedString and $token == '"') {" & @CRLF & _
" $insideDoubleQuotedString = false;" & @CRLF & _
" } elseif (!$insideDoubleQuotedString and $token != '"') {" & @CRLF & _
" if ($token[0] == T_STRING" & @CRLF & _
" //and ($token[1] != $className or $tokens[$i - 2][0] == T_NEW )" & @CRLF & _
" //and $token[1] != $methodName" & @CRLF & _
" ) {" & @CRLF & _
" if (" & @CRLF & _
" !in_array($token[1], $propertyNames) //TODO: property name equals name of a function or method" & @CRLF & _
" and !in_array($token[1], $this->constants) //TODO: constant name equals name of a function or method" & @CRLF & _
" and $token[1] != 'true'" & @CRLF & _
" and $token[1] != 'false'" & @CRLF & _
" and $token[1] != 'null'" & @CRLF & _
" ) {" & @CRLF & _
" $previousPreviousPreviousToken = $tokens[ $i - 3 ];" & @CRLF & _
" $previousPreviousToken = $tokens[ $i - 2 ];" & @CRLF & _
" $previousToken = $tokens[ $i - 1 ];" & @CRLF & _
" $nextToken = $tokens[ $i + 1 ];" & @CRLF & _
" $tokenAfterNext = $tokens[ $i + 2 ];" & @CRLF & _
"" & @CRLF & _
" $this->info($this->getTokenValues($tokens, $i));" & @CRLF & _
"" & @CRLF & _
"" & @CRLF & _
" if ($nextToken[0] == T_DOUBLE_COLON) {" & @CRLF & _
" // beginning of a call to a static method" & @CRLF & _
" //nop" & @CRLF & _
" continue;" & @CRLF & _
" } elseif (" & @CRLF & _
" (" & @CRLF & _
" $tokens[ $i - 4][0] == T_CATCH" & @CRLF & _
" and $previousPreviousPreviousToken[0] == T_WHITESPACE" & @CRLF & _
" and $previousPreviousToken == '('" & @CRLF & _
" and $previousToken[0] == T_WHITESPACE" & @CRLF & _
" )" & @CRLF & _
" or" & @CRLF & _
" (" & @CRLF & _
" $previousPreviousPreviousToken[0] == T_CATCH" & @CRLF & _
" and $previousPreviousToken[0] == T_WHITESPACE" & @CRLF & _
" and $previousToken == '('" & @CRLF & _
" )" & @CRLF & _
" or" & @CRLF & _
" (" & @CRLF & _
" $previousPreviousToken[0] == T_CATCH" & @CRLF & _
" and $previousToken == '('" & @CRLF & _
" )" & @CRLF & _
" ){" & @CRLF & _
" // catch block" & @CRLF & _
" continue;" & @CRLF & _
" } elseif ($previousPreviousToken[0] == T_NEW){" & @CRLF & _
" $this->debug('Found object creation with new operator');" & @CRLF & _
" if (!$this->showExternalCalls) {" & @CRLF & _
" continue;" & @CRLF & _
" }" & @CRLF & _
" $calleeClass = $token[1];" & @CRLF & _
" if (isset($this->codeSummary['classes'][$calleeClass])) {" & @CRLF & _
" // find constructor method" & @CRLF & _
" if (isset($this->codeSummary['classes'][$calleeClass]['methods']['__construct'])) {" & @CRLF & _
" $calleeName = "$calleeClass::__construct"" & @CRLF & _
" . $this->generateParametersForSignature($this->codeSummary['classes'][$calleeClass]['methods']['__construct']['params']);" & @CRLF & _
" } elseif (isset($this->codeSummary['classes'][$calleeClass]['methods'][$calleeClass])) {" & @CRLF & _
" $calleeName = "$calleeClass::$calleeClass"" & @CRLF & _
" . $this->generateParametersForSignature($this->codeSummary['classes'][$calleeClass]['methods'][$calleeClass]['params']);" & @CRLF & _
" } else {" & @CRLF & _
" $calleeName = "$calleeClass::__construct()";" & @CRLF & _
" }" & @CRLF & _
" $calleeFile = $this->codeSummary['classes'][$calleeClass]['file'];" & @CRLF & _
" } else {" & @CRLF & _
" // TODO: decide how this case should be handled (could be a PEAR class or a class of a PHP extension, e.g. GTK)" & @CRLF & _
" //if ($this->showInternalFunctions)" & @CRLF & _
" $calleeName = "$calleeClass::__construct()"; // TODO: it could also be $calleeClass::$calleeClass() implemented in PHP4-style, however if we don't have the code, we can't now" & @CRLF & _
" $calleeFile = '';" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" $this->info($this->getTokenValues($tokens, $i));" & @CRLF & _
" $this->recordVariableAsType($calleeClass, $tokens[$i-6][1]);" & @CRLF & _
" } elseif (" & @CRLF & _
" (" & @CRLF & _
" isset($previousPreviousToken[1]) and $previousPreviousToken[1] == '$this'" & @CRLF & _
" and $previousToken[0] == T_OBJECT_OPERATOR" & @CRLF & _
" and in_array($token[1], $methodNames)" & @CRLF & _
" )" & @CRLF & _
" or" & @CRLF & _
" (" & @CRLF & _
" isset($previousPreviousToken[1]) and $previousPreviousToken[1] == 'self'" & @CRLF & _
" and $previousToken[0] == T_DOUBLE_COLON" & @CRLF & _
" and in_array($token[1], $methodNames)" & @CRLF & _
" )" & @CRLF & _
" ){" & @CRLF & _
" $this->info('internal method call ($this-> and self:: and $className::)');" & @CRLF & _
" $calleeName = "$className::{$token[1]}" . $this->generateParametersForSignature($this->codeSummary['classes'][$className]['methods'][$token[1]]['params']);" & @CRLF & _
" $calleeFile = $file;" & @CRLF & _
" } elseif ($previousToken[0] == T_OBJECT_OPERATOR) {" & @CRLF & _
" $this->debug('External method call or property access'); " & @CRLF & _
" //TODO: what if a object holds another instance of its class" & @CRLF & _
" if (!$this->showExternalCalls) {" & @CRLF & _
" continue;" & @CRLF & _
" }" & @CRLF & _
" if ($nextToken == '(' or ($nextToken[0] == T_WHITESPACE and $tokenAfterNext == '(')) {" & @CRLF & _
" $calleeName = $token[1];" & @CRLF & _
" $this->debug("Calling for $calleeName");" & @CRLF & _
"" & @CRLF & _
" $variable = $tokens[$i-2][1];" & @CRLF & _
" $this->debug("Variable = $variable");" & @CRLF & _
" $calleeClass = $this->lookupTypeForVariable($variable);" & @CRLF & _
" $this->debug('found as ' . $calleeClass);" & @CRLF & _
" if ($calleeClass) {" & @CRLF & _
" $calleeParams = $this->generateParametersForSignature(" & @CRLF & _
" $this->codeSummary['classes'][$calleeClass]['methods'][$calleeName]['params']" & @CRLF & _
" );" & @CRLF & _
" $calleeFile = $this->codeSummary['classes'][$calleeClass]['file'];" & @CRLF & _
" } else {" & @CRLF & _
" if (" & @CRLF & _
" isset($this->methodLookupTable[$calleeName])" & @CRLF & _
" and count($this->methodLookupTable[$calleeName]) == 1" & @CRLF & _
" ) {" & @CRLF & _
" // there is only one class having a method with this name" & @CRLF & _
" // SMELL: but if the user only registers part of a system the only one hit " & @CRLF & _
" // is not necessarily valid." & @CRLF & _
" $calleeClass = $this->methodLookupTable[$calleeName][0];" & @CRLF & _
" if (isset($this->codeSummary['classes'][$calleeClass])) {" & @CRLF & _
" $calleeParams = $this->generateParametersForSignature(" & @CRLF & _
" $this->codeSummary['classes'][$calleeClass]['methods'][$calleeName]['params']" & @CRLF & _
" );" & @CRLF & _
" $calleeFile = $this->codeSummary['classes'][$calleeClass]['file'];" & @CRLF & _
"" & @CRLF & _
" $this->info("RECORDING CLASS OF $previousPreviousToken[1] VARIABLE to be $calleeClass\n");" & @CRLF & _
" $this->info($this->getTokenValues($tokens, $i));" & @CRLF & _
"" & @CRLF & _
" } else {" & @CRLF & _
" $this-warning("calleeClass is unset");" & @CRLF & _
" $calleeParams = null;" & @CRLF & _
" $calleeFile = null;" & @CRLF & _
" }" & @CRLF & _
" } else {" & @CRLF & _
" $numEntries = count($this->methodLookupTable[$calleeName]);" & @CRLF & _
" if ($numEntries == 0) {" & @CRLF & _
" $this->warning("method $calleeName was called, but I have no record for that");" & @CRLF & _
" } else {" & @CRLF & _
" $this->warning("I have $numEntries for $calleeName!");" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" $this->info($this->getTokenValues($tokens, $i));" & @CRLF & _
"" & @CRLF & _
" $calleeClass = '';" & @CRLF & _
" $calleeParams = '()';" & @CRLF & _
" $calleeFile = '';" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" $calleeName = "$calleeClass::$calleeName$calleeParams";" & @CRLF & _
" } else {" & @CRLF & _
" $this->info("Property access");" & @CRLF & _
" continue;" & @CRLF & _
" }" & @CRLF & _
" } elseif ($previousToken[0] == T_DOUBLE_COLON){" & @CRLF & _
" $this->debug("static external method call");" & @CRLF & _
" if (!$this->showExternalCalls) {" & @CRLF & _
" continue;" & @CRLF & _
" }" & @CRLF & _
" if ($nextToken != '(' and !($nextToken[0] == T_WHITESPACE and $tokenAfterNext == '(')) {" & @CRLF & _
" // constant access" & @CRLF & _
" continue;" & @CRLF & _
" }" & @CRLF & _
" $calleeClass = $previousPreviousToken[1];" & @CRLF & _
" $calleeMethod = $token[1];" & @CRLF & _
" $calleeFile = '';" & @CRLF & _
" $calleeParams = '()';" & @CRLF & _
" // parent::" & @CRLF & _
" if ($calleeClass == 'parent' and !empty($this->codeSummary['classes'][$className]['parentClass'])) {" & @CRLF & _
" $calleeClass = $this->codeSummary['classes'][$className]['parentClass'];" & @CRLF & _
" }" & @CRLF & _
" if (isset($this->codeSummary['classes'][$calleeClass])) {" & @CRLF & _
" $calleeFile = $this->codeSummary['classes'][$calleeClass]['file'];" & @CRLF & _
" if (isset($this->codeSummary['classes'][$calleeClass]['methods'][$calleeMethod]['params'])) {" & @CRLF & _
" $calleeParams = $this->generateParametersForSignature($this->codeSummary['classes'][$calleeClass]['methods'][$calleeMethod]['params']);" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" $calleeName = "$calleeClass::$calleeMethod$calleeParams";" & @CRLF & _
" //TODO: handle self::myMethod(); $className::myMethod(); here => abolish internal method call case" & @CRLF & _
" } else {" & @CRLF & _
"" & @CRLF & _
" $calledFunction = $token[1];" & @CRLF & _
" $calleeFile = '';" & @CRLF & _
" $calleeParams = '()';" & @CRLF & _
" " & @CRLF & _
" $this->info("Function call: ".$calledFunction);" & @CRLF & _
"" & @CRLF & _
" if (in_array($calledFunction, $this->internalFunctions)) {" & @CRLF & _
" if (!$this->showInternalFunctions) {" & @CRLF & _
" continue;" & @CRLF & _
" }" & @CRLF & _
" } else {" & @CRLF & _
" if (!$this->showExternalCalls) {" & @CRLF & _
" continue;" & @CRLF & _
" }" & @CRLF & _
" if (isset($this->codeSummary['functions'][$calledFunction])) {" & @CRLF & _
" $calleeFile = $this->codeSummary['functions'][$calledFunction]['file'];" & @CRLF & _
" if (isset($this->codeSummary['functions'][$calledFunction]['params'])) {" & @CRLF & _
" $calleeParams = $this->generateParametersForSignature($this->codeSummary['functions'][$calledFunction]['params']);" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" $calleeName = $calledFunction . $calleeParams;" & @CRLF & _
" }" & @CRLF & _
" $this->debug("---> $calleeName called from line $lineNumber and defined in $calleeFile");" & @CRLF & _
" $this->driver->addCall($lineNumber, $calleeFile, $calleeName);" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" } else {" & @CRLF & _
" //TODO: parse calls inside double quoted strings" & @CRLF & _
" $this->info(' ignoring code inside ""');" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" $this->debug('== endFunction ==');" & @CRLF & _
" $this->driver->endFunction();" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
"}" & @CRLF & _
"" & @CRLF & _
" public function generateParametersForSignature($parameters) {" & @CRLF & _
" $result = '(';" & @CRLF & _
" if (!empty($parameters)) {" & @CRLF & _
" foreach($parameters as $parameterName => $parameter) {" & @CRLF & _
" if ($parameter['byReference']) {" & @CRLF & _
" $result.= '&';" & @CRLF & _
" }" & @CRLF & _
" $result.= '$' . $parameterName . ', ';" & @CRLF & _
" }" & @CRLF & _
" $result = substr($result, 0, -2);" & @CRLF & _
" }" & @CRLF & _
" $result.= ')';" & @CRLF & _
" return $result;" & @CRLF & _
" }" & @CRLF & _
"}" & @CRLF & _
" public function __toString() {" & @CRLF & _
" return $this->driver->__toString();" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" public function save($file) {" & @CRLF & _
" return file_put_contents($file, $this->__toString());" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" " & @CRLF & _
" protected function getTokenValues($tokens, $i) {" & @CRLF & _
" $width = 10;" & @CRLF & _
" $pad = ' ';" & @CRLF & _
" $out = "\n";" & @CRLF & _
" $start = -5;" & @CRLF & _
" $end = 4;" & @CRLF & _
" $headerLine = '';" & @CRLF & _
" $rowLine = '';" & @CRLF & _
" $tokenLine = '';" & @CRLF & _
" for ($j = $start; $j <= $end; $j++) {" & @CRLF & _
" $n = ($i + $j);" & @CRLF & _
" $currentToken = $tokens[$n];" & @CRLF & _
" $tokenType = '';" & @CRLF & _
" $cell = '';" & @CRLF & _
" $header = '';" & @CRLF & _
" if (is_array($currentToken)) {" & @CRLF & _
" $mainValue = $currentToken[1];" & @CRLF & _
" $mainValue = str_replace("\n", '\n', $mainValue);" & @CRLF & _
" $mainValue = str_replace("\t", '\t', $mainValue);" & @CRLF & _
" if ($mainVlaue = '') {" & @CRLF & _
" $mainValue = 'nil';" & @CRLF & _
" } " & @CRLF & _
" $cell = $mainValue;" & @CRLF & _
" $tokenType = token_name($currentToken[0]);" & @CRLF & _
" } else {" & @CRLF & _
" $cell = '"'.$currentToken.'"';" & @CRLF & _
" }" & @CRLF & _
" $cell = $cell;" & @CRLF & _
" $cell = str_pad($cell, $width, $pad);" & @CRLF & _
" $tokenType = str_pad(substr($tokenType,0,$width), $width, $pad);" & @CRLF & _
" $header = '['.$j.']';" & @CRLF & _
" $header = str_pad($header, $width, $pad);" & @CRLF & _
"" & @CRLF & _
" $headerLine .= $header.' ';" & @CRLF & _
" $rowLine .= $cell.' ';" & @CRLF & _
" $tokenLine .= $tokenType.' ';" & @CRLF & _
" }" & @CRLF & _
" $out .= $headerLine."\n";" & @CRLF & _
" $out .= $rowLine."\n";" & @CRLF & _
" $out .= $tokenLine."\n";" & @CRLF & _
" return $out;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" protected $variableTypes = array();" & @CRLF & _
" protected function recordVariableAsType($class, $variable) {" & @CRLF & _
" $this->debug("RECORDING $variable AS TYPE $class");" & @CRLF & _
" $this->variableTypes[$variable] = $class;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * Returns a recorded type for a given variable, unless the variable is named '$self'" & @CRLF & _
" * in which case returns the class name." & @CRLF & _
" * If no match is found returns null." & @CRLF & _
" */" & @CRLF & _
" protected function lookupTypeForVariable($variable) {" & @CRLF & _
" $recordedType = $this->variableTypes[$variable];" & @CRLF & _
" if ($recordedType) {" & @CRLF & _
" $type = $recordedType;" & @CRLF & _
" } else {" & @CRLF & _
" $this->warning("No recorded type for $variable");" & @CRLF & _
" $type = null;" & @CRLF & _
" }" & @CRLF & _
" $this->info('LOOKUP FOR '.$variable.' returns '.$type);" & @CRLF & _
" return $type;" & @CRLF & _
" }" & @CRLF & _
"}" & @CRLF & _
"?>" & @CRLF & _
"<?php" & @CRLF & _
"/**" & @CRLF & _
" * Implementation of a call graph generation strategy wich renders a graph with" & @CRLF & _
" * dot." & @CRLF & _
" *" & @CRLF & _
" * PHP version 5" & @CRLF & _
" *" & @CRLF & _
" * This file is part of phpCallGraph." & @CRLF & _
" *" & @CRLF & _
" * PHPCallGraph is free software; you can redistribute it and/or modify" & @CRLF & _
" * it under the terms of the GNU General Public License as published by" & @CRLF & _
" * the Free Software Foundation; either version 3 of the License, or" & @CRLF & _
" * (at your option) any later version." & @CRLF & _
" *" & @CRLF & _
" * PHPCallGraph is distributed in the hope that it will be useful," & @CRLF & _
" * but WITHOUT ANY WARRANTY; without even the implied warranty of" & @CRLF & _
" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the" & @CRLF & _
" * GNU General Public License for more details." & @CRLF & _
" *" & @CRLF & _
" * You should have received a copy of the GNU General Public License" & @CRLF & _
" * along with this program. If not, see <http://www.gnu.org/licenses/>." & @CRLF & _
" *" & @CRLF & _
" * @package PHPCallGraph" & @CRLF & _
" * @author Falko Menge <fakko at users dot sourceforge dot net>" & @CRLF & _
" * @copyright 2007-2009 Falko Menge" & @CRLF & _
" * @license http://www.gnu.org/licenses/gpl.txt GNU General Public License" & @CRLF & _
" */" & @CRLF & _
"" & @CRLF & _
"// reduce warnings because of PEAR dependency" & @CRLF & _
"error_reporting(E_ALL ^ E_NOTICE);" & @CRLF & _
"" & @CRLF & _
"require_once 'CallgraphDriver.php';" & @CRLF & _
"require_once 'Image/GraphViz.php';" & @CRLF & _
"" & @CRLF & _
"/**" & @CRLF & _
" * Implementation of a call graph generation strategy wich renders a graph with" & @CRLF & _
" * dot." & @CRLF & _
" */" & @CRLF & _
"class GraphVizDriver implements CallgraphDriver {" & @CRLF & _
"" & @CRLF & _
" protected $outputFormat;" & @CRLF & _
" protected $dotCommand;" & @CRLF & _
" protected $useColor = true;" & @CRLF & _
" protected $graph;" & @CRLF & _
" protected $currentCaller = '';" & @CRLF & _
" protected $internalFunctions;" & @CRLF & _
"}" & @CRLF & _
" /**" & @CRLF & _
" * @return CallgraphDriver" & @CRLF & _
" */" & @CRLF & _
" public function __construct($outputFormat = 'png', $dotCommand = 'dot') {" & @CRLF & _
" $this->initializeNewGraph();" & @CRLF & _
" $this->setDotCommand($dotCommand);" & @CRLF & _
" $this->setOutputFormat($outputFormat);" & @CRLF & _
" $functions = get_defined_functions();" & @CRLF & _
" $this->internalFunctions = $functions['internal'];" & @CRLF & _
" " & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" public function reset() {" & @CRLF & _
" $this->initializeNewGraph();" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" protected function initializeNewGraph() {" & @CRLF & _
" $this->graph = new Image_GraphViz(" & @CRLF & _
" true," & @CRLF & _
" array(" & @CRLF & _
" 'fontname' => 'Verdana'," & @CRLF & _
" 'fontsize' => 12.0," & @CRLF & _
" //'fontcolor' => 'gray5'," & @CRLF & _
" 'rankdir' => 'LR', // left-to-right" & @CRLF & _
" )" & @CRLF & _
" );" & @CRLF & _
" $this->graph->dotCommand = $this->dotCommand;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * Sets path to GraphViz/dot command" & @CRLF & _
" * @param string $dotCommand Path to GraphViz/dot command" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" public function setDotCommand($dotCommand = 'dot') {" & @CRLF & _
" $this->dotCommand = $dotCommand;" & @CRLF & _
" $this->graph->dotCommand = $dotCommand;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * Sets output format" & @CRLF & _
" * @param string $outputFormat One of the output formats supported by GraphViz/dot" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" public function setOutputFormat($outputFormat = 'png') {" & @CRLF & _
" $this->outputFormat = $outputFormat;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * Enables or disables the use of color" & @CRLF & _
" * @param boolean $boolean True if color should be used" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" public function setUseColor($boolean = true) {" & @CRLF & _
" $this->useColor = $boolean;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * @param integer $line" & @CRLF & _
" * @param string $file" & @CRLF & _
" * @param string $name" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" public function startFunction($line, $file, $name, $memberCode) {" & @CRLF & _
" $this->addNode($name);" & @CRLF & _
" $this->currentCaller = $name;" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * @param integer $line" & @CRLF & _
" * @param string $file" & @CRLF & _
" * @param string $name" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" public function addCall($line, $file, $name) {" & @CRLF & _
" $this->addNode($name);" & @CRLF & _
" $this->graph->addEdge(array($this->currentCaller => $name));" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" protected function addNode($name) {" & @CRLF & _
" $nameParts = explode('::', $name);" & @CRLF & _
" $cluster = 'default';" & @CRLF & _
" $label = $name;" & @CRLF & _
" $color = 'lavender'; //lightblue2, lightsteelblue2, azure2, slategray2" & @CRLF & _
" if (count($nameParts) == 2) { // method call" & @CRLF & _
" if (empty($nameParts[0])) {" & @CRLF & _
" $cluster = 'class is unknown';" & @CRLF & _
" } else {" & @CRLF & _
" $cluster = $nameParts[0];" & @CRLF & _
" }" & @CRLF & _
" // obtain method name" & @CRLF & _
" $label = $nameParts[1];" & @CRLF & _
" }" & @CRLF & _
" // remove parameter list" & @CRLF & _
" $label = substr($label, 0, strpos($label, '('));" & @CRLF & _
"" & @CRLF & _
" if (count($nameParts) == 1) { // function call" & @CRLF & _
" if (in_array($label, $this->internalFunctions)) { // call to internal function" & @CRLF & _
" $cluster = 'internal PHP functions';" & @CRLF & _
" }" & @CRLF & _
" }" & @CRLF & _
" $this->graph->addNode(" & @CRLF & _
" $name," & @CRLF & _
" array(" & @CRLF & _
" 'fontname' => 'Verdana'," & @CRLF & _
" 'fontsize' => 12.0," & @CRLF & _
" //'fontcolor' => 'gray5'," & @CRLF & _
" 'label' => $label," & @CRLF & _
" //'style' => 'rounded' . ($this->useColor ? ',filled' : ''), // produces errors in rendering" & @CRLF & _
" 'style' => ($this->useColor ? 'filled' : 'rounded')," & @CRLF & _
" 'color' => ($this->useColor ? $color : 'black')," & @CRLF & _
" 'shape' => ($this->useColor ? 'ellipse' : 'rectangle')," & @CRLF & _
" )," & @CRLF & _
" $cluster" & @CRLF & _
" );" & @CRLF & _
" //*" & @CRLF & _
" $this->graph->addCluster(" & @CRLF & _
" $cluster," & @CRLF & _
" $cluster," & @CRLF & _
" array(" & @CRLF & _
"// 'style' => ($this->useColor ? 'filled' : '')," & @CRLF & _
" 'color' => 'gray20'," & @CRLF & _
"// 'bgcolor' => ''," & @CRLF & _
" )" & @CRLF & _
" );" & @CRLF & _
" //*/" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * @return void" & @CRLF & _
" */" & @CRLF & _
" public function endFunction() {" & @CRLF & _
" }" & @CRLF & _
"" & @CRLF & _
" /**" & @CRLF & _
" * @return string" & @CRLF & _
" */" & @CRLF & _
" public function __toString() {" & @CRLF & _
" return $this->graph->fetch($this->outputFormat);" & @CRLF & _
" }" & @CRLF & _
"}" & @CRLF & _
"?>" & @CRLF & _
""
Local $aArray = StringRegExp($sString, $sRegex, $STR_REGEXPARRAYGLOBALFULLMATCH)
Local $aFullArray[0]
For $i = 0 To UBound($aArray) -1
_ArrayConcatenate($aFullArray, $aArray[$i])
Next
$aArray = $aFullArray
; Present the entire match result
_ArrayDisplay($aArray, "Result")
Please keep in mind that these code samples are automatically generated and are not guaranteed to work. If you find any syntax errors, feel free to submit a bug report. For a full regex reference for AutoIt, please visit: https://www.autoitscript.com/autoit3/docs/functions/StringRegExp.htm