public function parse()

in src/parser/PhutilDocblockParser.php [43:161]


  public function parse($docblock) {
    // Strip off comments.
    $docblock = trim($docblock);
    $docblock = preg_replace('@^/\*\*@', '', $docblock);
    $docblock = preg_replace('@\*/$@', '', $docblock);
    $docblock = preg_replace('@^\s*\*@m', '', $docblock);

    // Normalize multi-line @specials.
    $lines = explode("\n", $docblock);
    $last = false;
    foreach ($lines as $k => $line) {

      // NOTE: We allow "@specials" to be preceded by up to two whitespace
      // characters; more than that and we assume the block is a code block.
      // Broadly, there's ambiguity between a special like:
      //
      //     <... lots of indentation ...> @author alincoln
      //
      // ...and a code block like:
      //
      //     <... lots of indentation ...> @def square(x, y):
      //
      // Because standard practice is to indent the entire block one level,
      // we allow that and one additional space before assuming something is
      // a code block.

      if (preg_match('/^\s{0,2}@\w/i', $line)) {
        $last = $k;
        $lines[$last] = trim($line);
      } else if (preg_match('/^\s*$/', $line)) {
        $last = false;
      } else if ($last !== false) {
        $lines[$last] = $lines[$last].' '.trim($line);
        unset($lines[$k]);
      }
    }

    $docblock = implode("\n", $lines);

    $special = array();

    // Parse @specials.
    $matches = null;
    $have_specials = preg_match_all(
      '/^@([\w-]+)[ \t]*([^\n]*)/m',
      $docblock,
      $matches,
      PREG_SET_ORDER);

    if ($have_specials) {
      $docblock = preg_replace(
        '/^@([\w-]+)[ \t]*([^\n]*)?\n*/m',
        '',
        $docblock);
      foreach ($matches as $match) {
        list($_, $type, $data) = $match;
        $data = trim($data);

        // For flags like "@stable" which don't have any string data, set the
        // value to true.
        if (!strlen($data)) {
          $data = true;
        }

        if (!isset($special[$type])) {
          $special[$type] = $data;
        } else {
          if (!is_array($special[$type])) {
            $special[$type] = (array)$special[$type];
          }
          $special[$type][] = $data;
        }
      }
    }

    // Convert `array(true, true, true)` to `true`.
    foreach ($special as $type => $data) {
      if (is_array($data)) {
        $all_trues = true;

        foreach ($data as $value) {
          if ($value !== true) {
            $all_trues = false;
            break;
          }
        }

        if ($all_trues) {
          $special[$type] = true;
        }
      }
    }

    $docblock = str_replace("\t", '  ', $docblock);

    // Smush the whole docblock to the left edge.
    $min_indent = 80;
    $indent = 0;
    foreach (array_filter(explode("\n", $docblock)) as $line) {
      for ($ii = 0; $ii < strlen($line); $ii++) {
        if ($line[$ii] != ' ') {
          break;
        }
        $indent++;
      }
      $min_indent = min($indent, $min_indent);
    }

    $docblock = preg_replace(
      '/^'.str_repeat(' ', $min_indent).'/m',
      '',
      $docblock);
    $docblock = rtrim($docblock);
    // Trim any empty lines off the front, but leave the indent level if there
    // is one.
    $docblock = preg_replace('/^\s*\n/', '', $docblock);

    return array($docblock, $special);
  }