Index: parser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/api/parser.inc,v retrieving revision 1.41.2.41 diff -u -r1.41.2.41 parser.inc --- parser.inc 2 Feb 2010 19:12:38 -0000 1.41.2.41 +++ parser.inc 6 Feb 2010 18:01:45 -0000 @@ -6,6 +6,16 @@ * The PHP documentation parser that generates content for api.module. */ +module_load_include('inc', 'pgp', 'engine/pgp.parser'); +module_load_include('inc', 'pgp', 'engine/pgp.reader'); +module_load_include('inc', 'pgp', 'engine/pgp.writer'); +module_load_include('inc', 'pgp', 'engine/pgp.editor'); +module_load_include('inc', 'pgp', 'engine/pgp.list'); +module_load_include('inc', 'pgp', 'engine/pgp.object'); + +// Constant to allow for switching between API parser and Grammar Parser. +define('USE_PARSER', 'YES'); + function api_parse_file($callback, $file_path, $branch, $file_name) { $docblock = array( 'object_name' => $file_name, @@ -13,10 +23,14 @@ 'object_type' => 'file', 'file_name' => $file_name, 'title' => strpos($file_name, '/') ? substr($file_name, strrpos($file_name, '/') + 1) : $file_name, + 'summary' => '', 'documentation' => '', 'version' => '', 'modified' => filemtime($file_path), 'source' => str_replace(array("\r\n", "\r"), array("\n", "\n"), file_get_contents($file_path)), + 'content' => '', + 'start_line' => 0, + 'see' => '', ); $match = array(); @@ -59,6 +73,8 @@ * Read in the file at the given path and parse its documentation. */ function api_parse_php_file($docblock) { +if (USE_PARSER == 'NO') { + $docblock['code'] = api_format_php($docblock['source']); $docblocks = array($docblock); @@ -168,6 +184,195 @@ api_save_documentation($docblocks); } +else { + api_parse_php_file_with_pgp($docblock); +} +} + +/** + * Returns a PGPEditor object. (Singleton) + * + * @return PGPEditor + */ +function api_get_editor() { + static $editor; + if (!$editor) { + $editor = new PGPEditor(); + } + return $editor; +} + +/** + * Read in the file at the given path and parse its documentation. + * + * @param array $docblock + * An array of the documentation block. + */ +function api_parse_php_file_with_pgp($docblock) { + // Edit grammar statements. + $editor = api_get_editor(); + + // Build grammar statements. + $reader = $editor->getReader(); + $reader->setSnippet($docblock['source']); + $reader->addTokenNames(); + $reader->buildGrammar(); + + // Retrieve items of interest. + $statements = $reader->getStatements(); + if (!$statements) { + // This is a text file or template file with no functions, constants, etc. + $docblock['code'] = api_format_php($docblock['source']); + api_save_documentation(array($docblock)); + // Free up memory. + $reader->reset(); + pgp_log_memory_use('reset'); + return; + } + + // Reserve the first array slot for the file documentation block. + $docblocks = array(); + $docblock['code'] = api_format_php($docblock['source']); + $docblocks[] = $docblock; + + // Set default documenation block array for items other than the file. + $default_block = api_default_block($docblock); + + api_documentation_loop($statements, $default_block, $docblocks); // api_documentation_loop($statements, $default_block); + + // Free up memory. + $reader->reset(); + + api_save_documentation($docblocks); +} + +/** + * Build a list of documentation items. + * + * @param array $statements + * A PGPBody object of body statements. + * @param array $docblock + * The default documentation block item for the file. + * @param array $docblocks + * The array of documentation block items for the file. + */ +function api_documentation_loop($statements, $default_block, &$docblocks) { +// global $is_file_block; + + $editor = api_get_editor(); + + // Traverse statement list to gather documentation items. + $current = $statements->first(); + while ($current->next != NULL) { + $statement = $current->data; + $type = is_object($statement) ? $statement->type : $statement['type']; + // Common processing. + switch ($type) { +// case T_INTERFACE: + case T_CLASS: + $default_block['class'] = ''; + case T_FUNCTION: + case T_DEFINE: + case T_GLOBAL: +// $docblock = api_documentation_item($statement, $default_block); + $docblock = $default_block; + $docblock['object_type'] = $editor->statementTypeToString($statement); + $docblock['object_name'] = $default_block['class'] . $editor->statementOperandToText($statement); + $docblock['title'] = $editor->statementOperandToText($statement); + $docblock['start_line'] = $current->line; + + $docblock['content'] = $editor->commentToString($statement->comment); + unset($statement->comment); + $docblock['code'] = api_format_php("toString() ."\n?>"); + + if (in_array($statement->type, array(T_CLASS, T_FUNCTION))) { + $docblock['signature'] = $editor->functionGetSignature($statement); + } + break; + + case T_DOC_COMMENT: + $docblock = $default_block; + // Copied from api_documentation_comment. + $docblock['content'] = $editor->commentToString($statement); + if (strpos($docblock['content'], '@mainpage') !== FALSE) { + // This is here so the block passes the if block after the switch. + $docblock['object_type'] = 'mainpage'; + $docblock['object_name'] = $docblock['branch']; + } + if (strpos($docblock['content'], '@file') !== FALSE) { + $editor->getReader()->debugPrint2("found @file comment"); + $docblock['object_type'] = 'file'; // Redundant? + $docblock['content'] = str_replace('@file', '', $docblock['content']); + $docblock['documentation'] = api_format_documentation($docblock['content']); + $docblock['summary'] = api_documentation_summary($docblock['documentation']); + // TODO This assumes only one "unattached" doc comment in the file! + $docblocks[0]['documentation'] = $docblock['documentation']; + $docblocks[0]['summary'] = $docblock['summary']; + // Reset the docblock so we do not add it again to the list. + $docblock = array(); + } + elseif (strpos($docblock['content'], '@defgroup') !== FALSE) { + // This is here so the block passes the if block after the switch. + $docblock['object_type'] = 'group'; + $docblock['object_name'] = 'group_xxx'; + } + break; + + default: + $docblock = array(); + continue; + +// case T_CONST: +// case T_VAR: +// $docblock = api_documentation_global($statement, $branch_name, $file_name); +// break; + } + if ($docblock && $docblock['object_type'] != '') { + if ($docblock['object_name'] == '') { + dpm("empty name\n"); + dpm($docblock); + } + $docblocks[] = $docblock; + } + + // Additional recursive processing on statements with bodies. + switch ($type) { +// case T_INTERFACE: + case T_CLASS: + $default_block['class'] = $docblock['title'] . '::'; + case T_FUNCTION: + api_documentation_loop($statement->body, $default_block, $docblocks); + break; + } + $current = $current->next; + } +} + +/** + * Return default documentation block array. + * + * @param array $docblock + * An array of the documentation block. + * @return array + */ +function api_default_block($docblock) { + $default = array( + 'object_name' => '', + 'branch' => $docblock['branch'], + 'object_type' => '', + 'file_name' => $docblock['file_name'], + 'title' => '', + 'summary' => '', + 'documentation' => '', + 'code' => '', +// 'version' => '', // Is this needed in other items? + 'modified' => $docblock['modified'], // Only needed for 'file' item, but it simplifies parameters in other functions. + 'start_line' => 0, + 'see' => '', + 'class' => '', // TODO Added this + ); + return $default; +} /** * Find functions called in a formatted block of code. @@ -202,13 +407,15 @@ $dids = array(); // Look for @file block first so $docblocks[0] gets filled in before it is processed. +// foreach ($docblocks as $docblock) { +// if (preg_match('/' . API_RE_TAG_START . 'file/', $docblock['content'])) { +// $docblocks[0]['content'] = str_replace('@file', '', $docblock['content']); +// break; +// } +// } foreach ($docblocks as $docblock) { - if (preg_match('/' . API_RE_TAG_START . 'file/', $docblock['content'])) { - $docblocks[0]['content'] = str_replace('@file', '', $docblock['content']); - break; - } - } - foreach ($docblocks as $docblock) { +if (USE_PARSER == 'YES') { + if (preg_match('/' . API_RE_TAG_START . 'mainpage/', $docblock['content'])) { $mainpage_matches = array(); preg_match('/' . API_RE_TAG_START . 'mainpage (.*?)\n/', $docblock['content'], $mainpage_matches); @@ -234,11 +441,14 @@ continue; } + $docblock['parameters'] = ''; + $docblock['return_value'] = ''; + $docblock['see'] = ''; if (!empty($docblock['content'])) { // Find parameter definitions. $param_match = array(); $offset = 0; - $docblock['parameters'] = ''; +// $docblock['parameters'] = ''; while (preg_match('/' . API_RE_TAG_START . 'param(.*?)(?=\n' . API_RE_TAG_START . '|\n\n|$)/s', substr($docblock['content'], $offset), $param_match, PREG_OFFSET_CAPTURE)) { $docblock['content'] = str_replace($param_match[0][0], '', $docblock['content']); $docblock['parameters'] .= "\n\n". $param_match[1][0]; @@ -248,7 +458,7 @@ // Find return value definitions. $return_matches = array(); - $docblock['return_value'] = ''; +// $docblock['return_value'] = ''; preg_match_all('/' . API_RE_TAG_START . 'return(.*?)(\n' . API_RE_TAG_START . '|\n\n|$)/s', $docblock['content'], $return_matches, PREG_SET_ORDER); foreach ($return_matches as $return_match) { $docblock['content'] = str_replace($return_match[0], '', $docblock['content']); @@ -257,12 +467,13 @@ $docblock['return_value'] = api_format_documentation($docblock['return_value']); // Find @see lines. + $see_match = array(); // TODO Mis-named variable. $offset = 0; - $docblock['see'] = ''; - while (preg_match('/' . API_RE_TAG_START . 'see(.*?)(?=\n' . API_RE_TAG_START . '|\n\n|$)/s', substr($docblock['content'], $offset), $match, PREG_OFFSET_CAPTURE)) { - $docblock['content'] = str_replace($match[0][0], '', $docblock['content']); - $docblock['see'] .= "\n\n". $match[1][0]; - $offset = $match[0][1]; +// $docblock['see'] = ''; + while (preg_match('/' . API_RE_TAG_START . 'see(.*?)(?=\n' . API_RE_TAG_START . '|\n\n|$)/s', substr($docblock['content'], $offset), $see_match, PREG_OFFSET_CAPTURE)) { + $docblock['content'] = str_replace($see_match[0][0], '', $docblock['content']); + $docblock['see'] .= "\n\n". $see_match[1][0]; + $offset = $see_match[0][1]; } $docblock['see'] = api_format_documentation($docblock['see']); @@ -298,18 +509,19 @@ $docblock['documentation'] = api_format_documentation($docblock['content']); } - if (!isset($docblock['summary'])) { + if (!$docblock['summary']) { // if (!isset($docblock['summary'])) { $docblock['summary'] = api_documentation_summary($docblock['documentation']); } - $docblock['start_line'] = substr_count(substr($docblocks[0]['source'], 0, $docblock['code_start']), "\n"); - if (!isset($docblock['code'])) { - if (isset($docblock['code_end'])) { - $docblock['code'] = api_format_php(""); - } - else { - $docblock['code'] = ''; - } - } +// $docblock['start_line'] = substr_count(substr($docblocks[0]['source'], 0, $docblock['code_start']), "\n"); +// if (!isset($docblock['code'])) { +// if (isset($docblock['code_end'])) { +// $docblock['code'] = api_format_php(""); +// } +// else { +// $docblock['code'] = ''; +// } +// } +} // END of use_parser $did = db_result(db_query("SELECT did FROM {api_documentation} WHERE object_name = '%s' AND branch_id = %d AND object_type = '%s' AND file_name = '%s'", $docblock['object_name'], $docblocks[0]['branch']->branch_id, $docblock['object_type'], $docblock['file_name'])); if ($did > 0) { @@ -354,7 +566,7 @@ } $dids[] = $did; - } + } // END of foreach $old_dids = array_diff($old_dids, $dids); if (count($old_dids) > 0) { @@ -571,7 +783,7 @@ switch ($type) { case T_OPEN_TAG: case T_CLOSE_TAG: - $output .= ''. $value .''; + $output .= ''. $value .''; // TODO typo s/b boundary break; case T_COMMENT: