Файловый менеджер - Редактировать - C:/xampp/php/PEAR/PHPDoc/parser/PhpdocParserCore.php
Назад
<?php /** * Provides basic parser functions. * * Provides basic parser functions to extract doc comments, analyse tags and variable * declarations. * * @version $Id: PhpdocParserCore.php,v 1.9 2002/07/26 14:49:39 derick Exp $ */ class PhpdocParserCore extends PhpdocParserTags { /** * Scans code for documented and undocumented phpdoc keywords (classes, functions, class variables, uses, constants). * * This method is somewhat the heart of the phpdoc parser. It takes a string of * phpcode and extracts all classes, functions, class variables, uses (include and friends), * and constants (define) from it. Extract does not mean that the whole class or another element * gets extracted. It does not take the code from the class definition and it's opening * curly brace to the closing one. PHPDoc just extracts the class definition itself and * if available a trailing doc comment. This has some drawbacks: phpdoc can't handle * files that contain more than one class it wouldn't know which method/class variable belongs to * a certain class. It's possible to provide a workaround but phpdoc would slow down dramatically. * As PHPDoc does not have a real parser but does a simple grep using a bunch of regular expressions * there're indeed more limitations. Nevertheless I doubt that you'll have problems with "normal" code. * * The search algorithm looks pretty strange but belive me it's fast. I have tried several other ways * (really complex regexps >500 chars, preg_match_all + looking backwards for comments, ...) but none was * faster. This one takes 13s on my machine to scan the current (14/08/2000) code (7130 lines), the * big RegExp way took more than 5 Minutes, the preg_match_all + looking backwards 52s. * * @param string PHP code to scan. * @param mixed String of one keyword or array of keywords not to scan for. Known keywords are: * "classes", "functions", "variables", "uses", "consts". * @return array Hash of phpdoc elements found, indexed by "variables", "functions", "classes", "consts", "uses". * @see $PHP_BASE, $PHP_COMPLEX, $C_BASE, $C_COMPLEX, extractPhpdoc(), getModuleDoc() */ function getPhpdocParagraphs($phpcode, $keywords='none') { // what are we not looking for? if ( !is_array($keywords) ) { if ('none' == $keywords) $keywords = array (); else $keywords = array ( $keywords => true ); } $start = 0; $paragraphs = array( 'classes' => array(), 'functions' => array(), 'variables' => array(), 'consts' => array(), 'uses' => array(), 'modules' => array() ); // remember the documented elements to be able to compare with the list of all elements $functions = array(); $variables = array(); $constants = array(); $uses = array(); // // Module docs are somewhat more difficult to grep. Always // use this function. // if (!isset($keywords['modules'])) list($paragraphs['modules'], $phpcode) = $this->getModuleDoc($phpcode); else list( , $phpcode) = $this->getModuleDoc($phpcode); // // Find documented elements // while (true) { $start = strpos($phpcode, '/**', $start); if (0 == (int)$start && 'integer' != gettype($start) ) break; $end = strpos($phpcode, '*/', $start); $remaining = trim(substr($phpcode, $end + 2)); if ( !isset($keywords['classes']) && preg_match($this->PHP_COMPLEX['class'], $remaining, $regs) || preg_match($this->PHP_COMPLEX['class_extends'], $remaining, $regs)) { $paragraphs['classes'][] = array( 'name' => $regs[1], 'extends' => (isset($regs[2])) ? $regs[2] : '', 'doc' => $this->extractPhpdoc(substr($phpcode, $start + 3, ($end-$start) - 2)) ); $classes[$regs[1]] = true; } else if ( !isset($keywords['functions']) && preg_match($this->PHP_COMPLEX['function'], $remaining, $regs)) { $head = substr($remaining, strpos($remaining, $regs[0]) + strlen($regs[0])); $head = substr(trim($this->getValue($head, array('{' => true))), 0, -1); $paragraphs['functions'][] = array( 'name' => $regs[1], 'doc' => $this->extractPhpdoc( substr($phpcode, $start+3, ($end-$start)-2) ), 'head' => $head ); $functions[$regs[1]] = true; } else if ( !isset($keywords['variables']) && preg_match($this->PHP_COMPLEX['var'], $remaining, $regs)) { if ('=' == $regs[2]) $value = trim($this->getValue( substr($remaining, strpos($remaining, $regs[0]) + strlen($regs[0]) ), array( ';' => true))); else $value = ''; $paragraphs['variables'][] = array( 'name' => $regs[1], 'value' => $value, 'doc' => $this->extractPhpdoc(substr($phpcode, $start + 3, ($end-$start) - 2)) ); $variables[$regs[1]] = true; } else if ( !isset($keywords['consts']) && preg_match($this->PHP_COMPLEX['const'], $remaining, $regs) ) { $name = ('' != $regs[2]) ? substr($regs[1], 1, -1) : $regs[1]; if (isset($regs[5])) { if ($regs[5]) $case = "case insensitive, userdefined: '$regs[5]'"; else $case = "case sensitive, userdefined: '$regs[5]'"; } else { $case = 'default: case sensitive'; } $paragraphs['consts'][] = array( 'name' => $name, 'value' => ('' != $regs[4]) ? substr($regs[3], 1, -1) : $regs[3], 'case' => $case, 'doc' => $this->extractPhpdoc(substr($phpcode, $start + 3, ($end - $start) - 2)) ); $constants[$name] = true; } else if ( !isset($keywords['uses']) && preg_match($this->PHP_COMPLEX['use'], $remaining, $regs)) { $filename = isset($regs[5]) ? $regs[5] : $regs[4]; $paragraphs['uses'][] = array( 'type' => $regs[1], 'file' => $filename, 'doc' => $this->extractPhpdoc(substr($phpcode, $start + 3, ($end - $start) - 2)) ); $uses[$filename] = true; } $start++; } // // Find undocumented elements // if (!isset($keywords['classes'])) { preg_match_all($this->PHP_COMPLEX['undoc_class'], $phpcode, $regs, PREG_SET_ORDER); reset($regs); while (list($k, $data) = each($regs)) if (!isset($classes[$data[1]])) $paragraphs['classes'][] = array( 'name' => $data[1], 'extends' => '', 'doc' => '' ); preg_match_all($this->PHP_COMPLEX['undoc_class_extends'], $phpcode, $regs, PREG_SET_ORDER); reset($regs); while (list($k, $data) = each($regs)) if (!isset($classes[$data[1]])) $paragraphs['classes'][] = array( 'name' => $data[1], 'extends' => $data[2], 'doc' => '' ); } if (!isset($keywords['functions'])) { preg_match_all($this->PHP_COMPLEX['undoc_function'], $phpcode, $regs, PREG_SET_ORDER); reset($regs); while (list($k, $data) = each($regs)) if (!isset($functions[$data[1]])) { $head = substr($phpcode, strpos($phpcode, $data[0]) + strlen($data[0])); $head = substr(trim( $this->getValue($head, array( '{' => true) )), 0, -1); $paragraphs['functions'][] = array( 'name' => $data[1], 'doc' => '', 'head' => $head ); } } if (!isset($keywords['variables'])) { preg_match_all($this->PHP_COMPLEX['undoc_var'], $phpcode, $regs, PREG_SET_ORDER); reset($regs); while (list($k, $data) = each($regs)) if (!isset($variables[$data[1]])) { if ('=' == $data[2]) $value = trim($this->getValue( substr($phpcode, strpos($phpcode, $data[0]) + strlen($data[0]) ), array( ';' => true))); else $value = ''; $paragraphs['variables'][] = array( 'name' => $data[1], 'value' => $value, 'doc' => '' ); } } if (!isset($keywords['consts'])) { preg_match_all($this->PHP_COMPLEX['undoc_const'], $phpcode, $regs, PREG_SET_ORDER); reset($regs); while (list($k, $data)=each($regs)) { $name = (''!=$data[2]) ? substr($data[1], 1, -1) : $data[1]; if (!isset($constants[$name])) { if (isset($data[5])) { if ($data[5]) $case = "case insensitive, userdefined: '$data[5]'"; else $case = "case sensitive, userdefined: '$data[5]'"; } else { $case = 'default: case sensitive'; } $paragraphs['consts'][] = array( 'name' => $name, 'value' => ('' != $data[4]) ? substr($data[3], 1, -1) : $data[3], 'case' => $case, 'doc' => '' ); } } } if (!isset($keywords['uses'])) { preg_match_all($this->PHP_COMPLEX['undoc_use'], $phpcode, $regs, PREG_SET_ORDER); reset($regs); while (list($k, $data) = each($regs)) { $filename = isset($data[5]) ? $data[5] : $data[4]; if (!isset($uses[$filename])) { $paragraphs['uses'][] = array( 'type' => $data[1], 'file' => $filename, 'doc' => '' ); } } } return $paragraphs; } // end func getPhpdocParagraphs /** * Does a quick prescan to find modules and classes. * * @param string Code to scan * @return array Hash of modules and classes found in the given code * @access public * @see getPhpdocParagraphs() */ function getModulesAndClasses($phpcode) { $para = array(); list($para['modules'], $phpdcode) = $this->getModuleDoc($phpcode); $para['classes'] = $this->getClasses($phpcode); return $para; } // end func getModulesAndClasses /** * Tries to extract a module doc. * * The syntax for modules is not final yet. The implementation and meaning of 'module' * might change at every time! Please do not ask for implementation details. * * @param string PHP Code to scan * @return array $module structure: $module[0] = array with module data, * $module[1] = php code without the leading module doc */ function getModuleDoc($phpcode) { $module = array(); $rp = '@((//|#).*?$|(/\*\*.*?\*/)|/\*.*?\*/)@ms'; $rp2 = "@^.*?(<\?php|<\?|<%|<script\s+language\s*=\s*[\"']php[\"']\s*>)\s*/\*\*(.*?)\*/@s"; /* if (preg_match($this->C_COMPLEX['module_doc'], $phpcode, $regs) ) {*/ if (preg_match($rp2, $code = preg_replace ($rp, '\3', $phpcode), $regs) ) { $doc_comment = $regs[2]; // Do we have OO Code? If not, continue. if ( !preg_match($this->PHP_COMPLEX['class'], $code) && !preg_match($this->PHP_COMPLEX['class_extends'], $code) ) { // Is there a module tag? if ( preg_match($this->C_COMPLEX['module_tags'], $doc_comment) ) { $doc_comment = $this->extractPhpDoc($doc_comment); $tags = $this->getTags( $doc_comment); $allowed = array ( 'module' => true, 'modulegroup' => true ); $tags = $this->analyseTags( $tags, array(), array( 'module' => true, 'modulegroup' => true) ); $module = array ( 'doc' => $doc_comment, 'status' => 'ok', 'name' => (isset($tags['module'])) ? $tags['module'] : '', 'group' => (isset($tags['modulegroup'])) ? $tags['modulegroup'] : '' ); } else { // No module tag. // Try the remaining keywords. If one matches it's not a module doc // assume that the module doc is missing. If none matches assume that // it's a module doc which lacks the module tags. if (preg_match($this->PHP_COMPLEX['function'], $doc_comment) || preg_match($this->PHP_COMPLEX['use'], $doc_comment) || preg_match($this->PHP_COMPLEX['const'], $doc_comment) || preg_match($this->PHP_COMPLEX['var'], $doc_comment)) { $module = array( 'doc' => '', 'status' => 'missing', 'name' => '', 'group' => '' ); $remaining = $phpcode; } else { $module = array( 'doc' => $doc_comment, 'status' => 'tags missing', 'name' => '', 'group' => '' ); } } // end if module_tags } else { $remaining = $phpcode; } // end if class } else { $remaining = $phpcode; } if (!isset($remaining)) { $rp3 = "@^.*?(<\?php|<\?|<%|<script\s+language\s*=\s*[\"']php[\"']\s*>).*?/\*\*(.*?)\*/@s"; $remaining = preg_replace($rp3, "", $phpcode); } return array($module, $remaining); } // end func getModuleDoc /** * Returns a list of classes found in the given code. * * In early versions PHPdoc parsed all the code at once which restulted in huge * memory intensive hashes. Now it scans for classes, builds a classtree and * does the parsing step by step, writing information to the destination * (renderer, exporter) as soon as possible. This reduces the memory consumption * dramatically. getPhpdocParagraphs() could be used to extract the class definitions * as well but this specialized function is somewhat faster. * * @param string PHP code to scan. * @return array $classes Array of classes found in the code. $classes[classname] = extends */ function getClasses($phpcode) { $classes = array(); preg_match_all($this->PHP_COMPLEX['undoc_class'], $phpcode, $regs, PREG_SET_ORDER); reset($regs); while (list($k, $data) = each($regs)) $classes[] = array( 'name' => $data[1], 'extends' => '' ); preg_match_all($this->PHP_COMPLEX['undoc_class_extends'], $phpcode, $regs, PREG_SET_ORDER); reset($regs); while (list($k, $data) = each($regs)) $classes[] = array( 'name' => $data[1], 'extends' => $data[2] ); return $classes; } // end func getClasses /** * Strips '/xx', 'x/' and x from doc comments (x means asterix). * * @param string Doc comment to clean up. * @return string $phpdoc */ function extractPhpdoc($paragraph) { $lines = split( $this->PHP_BASE['break'], $paragraph); $phpdoc = ''; reset($lines); while (list($k, $line) = each($lines)) { if (preg_match('/^\s*$/', $line)) { continue; } if (preg_match('/^\s*\*/', $line)) $phpdoc .= preg_replace('/^\s*\*/', '', $line) . PHPDOC_LINEBREAK; else $phpdoc .= $line . PHPDOC_LINEBREAK; } return substr($phpdoc, 0, -1); } // end func extractPhpdoc /** * Extract the description from a PHPDoc doc comment. * * Every PHPDoc doc comment has the same syntax: /xx[break][x]short description * [break][[x]multiple line long description[break]][[x]@list of tags[. This function * returns an array of the short description and long description. * * @param string Doc comment to examine. * @return array $description $description[0] = short description (first line), * $description[1] = long description (second line upto the first tag) */ function getDescription($phpdoc) { // find the position of the first doc tag $positions = $this->getTagPos($phpdoc); if (0 == count($positions)) $desc = trim($phpdoc); // no doc tags else $desc = trim(substr($phpdoc, 0, $positions[0]['pos'])); // strip tags $lines = split($this->PHP_BASE['break'], $desc); // 3/8/2002 - Tim Gallagher<timg@sunflowerroad.com> changed this // section back to original behavior. // I don't like putting the short description with the long description. // it muddys the data, and doesn't allow the template author to move them around // separate of each other. Instead, what i've done is to put description and short description // together in my *templates*. I like this better because I usually start the description // with the first line of description, then continue the thought on to the next line(s) // this looks better in code too because it flows better. // plus, if there is no long description, the short one prints anyway. // I don't want to start a code war, so i've defined a variable to allow both behaviors. // look in prepend.php for that variable. if (PHPDOC_SEPARATE_DESCRIPTIONS) { $sdesc = trim($lines[0]); unset($lines[0]); $ldesc = implode(" ", $lines); $description = array ($sdesc, $ldesc); } else { if (count($lines) == 1 || $desc == '') { // only a short description but no long description - or even none of both $description = array ($desc, $desc); } else { $sdesc = trim($lines[0]); $description = array($sdesc, $desc); }; // end if }; // end if return $description; } // end func getDescription /** * Scans a code passage for a value. * * There some cases where you can hardly use a regex to grep a value * because the value might contain unescaped charaters that end the value. * Value means something like "array ( ";", '\;' );" or "'phpdoc; ';" where * the delimiter would be ';'. * * @param string The php code to examine. * @param mixed String of one delimiter or array of delimiters. * @return string Value found in the code * @todo Racecondition: comments */ function getValue($code, $delimiter) { if ('' == $code) return ''; if (!is_array($delimiter)) $delimiter = array( $delimiter => true ); $code = trim($code); $len = strlen($code); $enclosed = false; $enclosed_by = ''; if (isset($delimiter[$code[0]])) { $i = 1; } else { for ($i = 0; $i < $len; $i++) { $char = $code[$i]; if (('"' == $char || "'" == $char) && ($char == $enclosed_by || '' == $enclosed_by) && (0 == $i || ($i > 0 && "\\" != $code[$i - 1]))) { if (!$enclosed) $enclosed_by = $char; else $enclosed_by = ''; $enclosed = !$enclosed; } if (!$enclosed && isset($delimiter[$char])) break; } } return substr($code, 0, $i); } // end func getValue /** * Analyses a code snipped and returns the type and value of the first variable found. * * With version 0.3 PHPDoc tries to analyse variable declarations to find * type and value. This is used to analyse class variable declarations and * optional function arguments. * * Note that all regular expressions in this function start with '^'. That means * you have to do some preparations to the code snippet you're passing to this * function. * * @param string PHP code to analyse * @param boolean Flag indicating the 'type' of code to analyse. Optional * function parameters and class variables have a slightly * different syntax for arrays. By default function parameters are expected. * @return array $vartype $vartype[0] = type, $vartype[1] = value, $vartype[2] = raw value */ function getVariableTypeAndValue($code, $flag_args = true) { $type = 'unknown'; $value = 'unknown'; $raw_value = $code; // // Do not change the order the function tries to find out the type. // if (preg_match($this->PHP_COMPLEX['type_boolean'], $code, $regs)) { $type = 'boolean'; $raw_value = $regs[0]; $value = $regs[0]; } else if (preg_match($this->PHP_COMPLEX['type_string_enclosed'], $code, $regs)) { $type = 'string'; $raw_value = $regs[0]; $value = $regs[0]; } else if (preg_match($this->PHP_COMPLEX['type_int_oct'], $code, $regs)) { $type = 'integer (octal)'; $raw_value = $regs[0]; $value = preg_replace('@\s@', '', $regs[0]); if ((int)$value != $value) { $type .= ' [warning: out of integer range, possible overflow trouble]'; } $value = octdec($value)." ($value)"; } else if (preg_match($this->PHP_COMPLEX['type_int_hex'], $code, $regs)) { $type = 'integer (hexadecimal)'; $raw_value = $regs[0]; $value = preg_replace('@\s@', '', $regs[0]); if ((int)$value != $value) $type .= ' [warning: out of integer range, possible overflow trouble]'; $value = hexdec($value)." ($value)"; } else if (preg_match($this->PHP_COMPLEX['type_float_exponent'], $code, $regs)) { $type = 'float'; $raw_value = $regs[0]; $value = (string)preg_replace('@\s@', '', $regs[0]); if ((float)$value != $value) { $type .= ' [warning: out of float range]'; } $value = (float)$value; } else if (preg_match($this->PHP_COMPLEX['type_float'], $code, $regs)) { $type = 'float'; $raw_value = $regs[0]; $value = preg_replace('@\s@', '', $regs[0]); if ((float)$value != $value) { $type .= ' [warning: out of float range]'; } $value = (float)$value; } else if (preg_match($this->PHP_COMPLEX['type_number'], $code, $regs)) { $value = preg_replace('@\s@', '', $regs[0]); $raw_value = $regs[0]; if ((int)$value == $value) { $type = 'integer'; $value = (int)$value; } else { $type = 'float'; if ((float)$value != $value) { $type.=' [warning: out of float range]'; } $value = (float)$value; } } else if ($flag_args && preg_match($this->PHP_COMPLEX['type_empty_array'], $code, $regs)) { $value = 'array()'; $raw_value = $regs[0]; $type = 'array'; } else if (!$flag_args && preg_match($this->PHP_COMPLEX['type_array'], $code, $regs)) { $value = $this->getValue($code, array(';' => true)); // strpos() is twice as fast as substr() if (0 == strpos($value, 'array')) { $type = 'array'; } $raw_value == $value; } else if (preg_match($this->PHP_COMPLEX['type_empty_resource'], $code, $regs)) { $type = 'resource'; $value = $regs[0]; $raw_value = $regs[0]; } else if (preg_match($this->PHP_COMPLEX['type_constant'], $code, $regs)) { $constant = strtolower(trim($regs[0])); if ($constant == 'null' || $constant == 'null,') { $type = ''; $value = 'null'; $raw_value = 'null'; } else { $type = 'constant'; $raw_value = $regs[0]; $value = $regs[0]; } } return array($type, $value, $raw_value); } // end func getVariableTypeAndValue } // end class PhpdocParserObject ?>
| ver. 1.4 |
Github
|
.
| PHP 5.3.8 | Генерация страницы: 0.12 |
proxy
|
phpinfo
|
Настройка