Index: api.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/api/api.info,v
retrieving revision 1.6.2.1
diff -u -r1.6.2.1 api.info
--- api.info	7 Aug 2009 07:14:58 -0000	1.6.2.1
+++ api.info	20 Jan 2010 19:17:04 -0000
@@ -5,3 +5,4 @@
 core = 6.x
 dependencies[] = job_queue
 dependencies[] = node
+dependencies[] = pgp
Index: parser.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/api/parser.inc,v
retrieving revision 1.41.2.38
diff -u -r1.41.2.38 parser.inc
--- parser.inc	20 Jan 2010 07:11:43 -0000	1.41.2.38
+++ parser.inc	20 Jan 2010 19:17:05 -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,
@@ -59,6 +69,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 +180,371 @@
 
   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.
+ * @param array
+ *   An array of documentation block items.
+ */
+function api_parse_php_file_with_pgp($docblock) {
+  // Edit grammar statements.
+  $editor = api_get_editor();
+
+  $source = $docblock['source'];
+
+  // Build grammar statements.
+  $reader = $editor->getReader();
+  $reader->setSnippet($source);
+  $reader->addTokenNames();
+  $reader->buildGrammar();
+  $reader->setSnippet(); // Free up memory.
+
+  // 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($source);
+    api_save_documentation(array($docblock));
+    return;
+  }
+
+  // Set default documenation block array.
+  $default_block = api_default_block($docblock);
+
+  // Reserve the first array slot for the file documentation block.
+  global $docblocks;
+  $docblocks = array();
+  $docblocks[] = api_documentation_file($source, $docblock);
+  $source = ''; // Free up memory.
+
+  global $nested_groups;
+  $nested_groups = array();
+
+  api_documentation_loop($statements, $default_block);
+
+  $reader->setStatements(); // Free up memory.
+
+  $count = count($docblocks);
+  api_save_documentation($docblocks);
+
+  $docblocks = array(); // Free up memory.
+}
+
+/**
+ * Build a list of documentation items.
+ *
+ * @param array $statements
+ *   An array of body statements.
+ * @param array $default_block
+ *   The default documentation block item.
+ */
+function api_documentation_loop($statements, $default_block) {
+  global $docblocks;
+  global $is_file_block;
+
+  // 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:
+      case T_FUNCTION:
+      case T_DEFINE:
+      case T_GLOBAL:
+        $docblock = api_documentation_item($statement, $default_block);
+        break;
+
+      case T_DOC_COMMENT:
+        $docblock = api_documentation_comment($statement, $default_block);
+        if ($is_file_block) {
+          $is_file_block = FALSE;
+          $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();
+        }
+        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:
+      case T_FUNCTION:
+        api_documentation_loop($statement->body, $default_block);
+        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' => '',
+  );
+  return $default;
+}
+
+/**
+ * Add items to documentation block for a statement.
+ *
+ * @param PGPBase $statement
+ *   A grammar object of the statement block.
+ * @param array $docblock
+ *   An array of the documentation block.
+ * @return array
+ *   An array of the documentation block.
+ */
+function api_documentation_item($statement, $docblock) {
+  $editor = api_get_editor();
+
+  $docblock['object_type'] = $editor->statementTypeToString($statement);
+  $docblock['object_name'] = $editor->statementOperandToText($statement);
+  $docblock['title'] = $editor->statementOperandToText($statement);
+  $docblock['start_line'] = 0; // TODO
+  $docblock['see'] = '';
+
+  $docblock['content'] = $editor->commentToString($statement->comment);
+  unset($statement->comment);
+  $docblock['code'] = api_format_php("<?php\n". $statement->toString() ."\n?>");
+
+  if (in_array($statement->type, array(T_CLASS, T_FUNCTION))) {
+    $docblock['signature'] = $editor->functionGetSignature($statement);
+
+    // Find parameter definitions.
+    $matches = array();
+    $offset = 0;
+    $docblock['parameters'] = '';
+    // TODO This regex appears to be opportunistic about contents of next line.
+    while (preg_match('!@param(.*?)(?=\n@|\n\n|$)!s', substr($docblock['content'], $offset), $matches, PREG_OFFSET_CAPTURE)) {
+      $docblock['content'] = str_replace($matches[0][0], '', $docblock['content']);
+      $docblock['parameters'] .= "\n\n". $matches[1][0];
+      $offset = $matches[0][1];
+    }
+    $docblock['parameters'] = api_format_documentation($docblock['parameters']);
+
+    // Find return value definitions.
+    $matches = array();
+    $docblock['return_value'] = '';
+    preg_match_all('!@return(.*?)(\n@|\n\n|$)!s', $docblock['content'], $matches, PREG_SET_ORDER);
+    foreach ($matches as $match) {
+      $docblock['content'] = str_replace($match[0], '', $docblock['content']);
+      $docblock['return_value'] .= "\n\n". $match[1];
+    }
+    $docblock['return_value'] = api_format_documentation($docblock['return_value']);
+
+    // Find @see lines.
+    $matches = array();
+    $offset = 0;
+    $docblock['see'] = '';
+    while (preg_match('/' . API_RE_TAG_START . 'see(.*?)(?=\n' . API_RE_TAG_START . '|\n\n|$)/s', substr($docblock['content'], $offset), $matches, PREG_OFFSET_CAPTURE)) {
+      $docblock['content'] = str_replace($matches[0][0], '', $docblock['content']);
+      $docblock['see'] .= "\n\n". $matches[1][0];
+      $offset = $matches[0][1];
+    }
+    $docblock['see'] = api_format_documentation($docblock['see']);
+
+    // Find function calls.
+    $docblock['function calls'] = api_parse_function_calls($docblock['code']); // Add this helper routine!!!
+  }
+
+  $docblock['documentation'] = api_format_documentation($docblock['content']);
+
+  $docblock['summary'] = api_documentation_summary($docblock['documentation']);
+
+  // Determine group membership.
+  api_documentation_group($docblock);
+  api_documentation_nested_group($docblock);
+
+  return $docblock;
+}
+
+/**
+ * Add items to documentation block for a statement.
+ *
+ * @param sting $source
+ *   A string of the file.
+ * @param array $docblock
+ *   An array of the documentation block.
+ * @return array
+ *   An array of the documentation block.
+ */
+function api_documentation_file($source, $docblock) {
+  // The commented out items are now set in api_parse_file().
+//  $filename = $docblock['file_name'];
+
+//  $docblock['object_name'] = $filename;
+//  $docblock['object_type'] = 'file';
+//  $docblock['title'] = strpos($filename, '/') ? substr($filename, strrpos($filename, '/') + 1) : $filename;
+  $docblock['code'] = api_format_php($source);
+  $docblock['start_line'] = 0; // TODO
+  $docblock['see'] = '';
+
+//  $matches = array();
+//  if (preg_match('!\$'.'Id: .*?,v (.*?) (.*?) (.*?) (.*?) Exp \$!', $source, $matches)) {
+//    $docblock['version'] = $matches[1] . ' (checked in on ' . $matches[2] . ' at ' . $matches[3] . ' by ' . $matches[4] . ')';
+//  }
+
+  return $docblock;
+}
+
+/**
+ * Determine group membership.
+ *
+ * @param array $docblock
+ *   An array of the documentation block.
+ */
+function api_documentation_group(&$docblock) {
+  global $nested_groups;
+
+  $group_matches = array();
+  preg_match_all('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches);
+  $docblock['groups'] = $group_matches[2];
+  $docblock['content'] = preg_replace('!@ingroup.*?\n!', '', $docblock['content']);
+
+  foreach ($nested_groups as $group_id) {
+    if (!empty($group_id)) {
+      $docblock['groups'][] = $group_id;
+    }
+  }
+}
+
+/**
+ * Handle nested function groups.
+ *
+ * @param array $docblock
+ *   An array of the documentation block.
+ */
+function api_documentation_nested_group($docblock) {
+  global $nested_groups;
+
+  if (strpos($docblock['content'], '@{') !== FALSE) {
+    if ($docblock['object_type'] == 'group') {
+      array_push($nested_groups, $docblock['object_name']);
+    }
+    else {
+      $group_matches = array();
+      if (preg_match('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches)) {
+        array_push($nested_groups, $group_matches[2]);
+      }
+      else {
+        array_push($nested_groups, '');
+      }
+    }
+  }
+  if (strpos($docblock['content'], '@}') !== FALSE) {
+    array_pop($nested_groups);
+  }
+}
+
+/**
+ * Add items to documentation block for a comment.
+ *
+ * @param array $comment
+ *   An array of the comment.
+ * @param array $docblock
+ *   An array of the documentation block.
+ * @return array
+ *   An array of the documentation block.
+ */
+function api_documentation_comment($comment, $docblock) {
+  $editor = api_get_editor();
+  global $is_file_block;
+
+  $is_file_block = FALSE;
+  $docblock['content'] = $editor->commentToString($comment);
+
+  $matches = array();
+  if (strpos($docblock['content'], '@mainpage') !== FALSE) {
+    preg_match('!@mainpage (.*?)\n!', $docblock['content'], $matches);
+    $docblock['object_type'] = 'mainpage';
+    $docblock['object_name'] = $docblock['branch'];
+    $docblock['title'] = $matches[1];
+    $docblock['content'] = preg_replace('!@mainpage.*?\n!', '', $docblock['content']);
+  }
+  elseif (strpos($docblock['content'], '@file') !== FALSE) {
+    $is_file_block = TRUE;
+    $docblock['object_type'] = 'file'; // Redundant?
+    $docblock['content'] = str_replace('@file', '', $docblock['content']);
+  }
+  elseif (strpos($docblock['content'], '@defgroup') !== FALSE) {
+    if (preg_match('!@defgroup ([a-zA-Z0-9_.-]+) +(.*?)\n!', $docblock['content'], $matches)) {
+      $docblock['object_type'] = 'group';
+      $docblock['object_name'] = $matches[1];
+      $docblock['title'] = $matches[2];
+      $docblock['content'] = preg_replace('!@defgroup.*?\n!', '', $docblock['content']);
+    }
+    else {
+      watchdog('api', 'Malformed @defgroup in %file at line %line.', array('%file' => $file_path, '%line' => $docblock['start_line']), WATCHDOG_NOTICE);
+    }
+  }
+
+  if ($docblock['object_type'] != '') {
+    $docblock['documentation'] = api_format_documentation($docblock['content']);
+    $docblock['summary'] = api_documentation_summary($docblock['documentation']);
+  }
+
+  // Update groups.
+  api_documentation_nested_group($docblock);
+
+  return $docblock;
+}
 
 /**
  * Find functions called in a formatted block of code.
@@ -202,6 +579,8 @@
 
   $dids = array();
   foreach ($docblocks as $docblock) {
+if (USE_PARSER == 'NO') {
+
     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);
@@ -254,7 +633,7 @@
       $docblock['return_value'] = api_format_documentation($docblock['return_value']);
 
       // Find @see lines.
-      $param_match = array();
+      $param_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)) {
@@ -308,6 +687,8 @@
         $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) {
       db_query("UPDATE {api_documentation} SET title = '%s', file_name = '%s', summary = '%s', documentation = '%s', code = '%s', start_line = %d, see = '%s' WHERE did = %d", $docblock['title'], $docblock['file_name'], $docblock['summary'], $docblock['documentation'], $docblock['code'], $docblock['start_line'], $docblock['see'], $did);
@@ -351,7 +732,7 @@
     }
 
     $dids[] = $did;
-  }
+  } // END of foreach
 
   $old_dids = array_diff($old_dids, $dids);
   if (count($old_dids) > 0) {
@@ -568,7 +949,7 @@
       switch ($type) {
         case T_OPEN_TAG:
         case T_CLOSE_TAG:
-          $output .= '<span class="php-boundry">'. $value .'</span>';
+          $output .= '<span class="php-boundry">'. $value .'</span>'; // TODO typo s/b boundary
           break;
 
         case T_COMMENT:
