private static function processEmphasis()

in src/inlines/Emphasis.php [174:312]


  private static function processEmphasis(
    Context $context,
    vec<Stack\Node> $stack,
  ): vec<Stack\Node> {
    $position = 0;
    $openers_bottom = dict[
      '*' => vec[0, 0, 0], // indexed by number of chars in delimiter - [0] is unused, but convenient
      '_' => vec[0, 0, 0],
    ];

    while ($position < C\count($stack)) {
      $closer_idx = self::findCloser($stack, $position);
      if ($closer_idx === null) {
        break;
      }
      $position = $closer_idx;
      $closer = $stack[$closer_idx];
      invariant(
        $closer is Stack\DelimiterNode,
        'closer must be a delimiter',
      );
      list($closer_text, $closer_flags) = tuple(
        $closer->getText(),
        $closer->getFlags(),
      );
      $char = $closer_text[0];
      $opener = null;
      $closer_len = Str\length($closer->getText()) % 3;
      $bottom = $openers_bottom[$char][$closer_len];
      for ($i = $position - 1; $i >= $bottom; $i--) {
        $item = $stack[$i];
        if (!$item is Stack\DelimiterNode) {
          continue;
        }
        if (!($item->getFlags() & self::IS_START)) {
          continue;
        }
        if ($item->getText()[0] !== $char) {
          continue;
        }

        // intra-word delimiters must match exactly
        // e.g.
        //  - `*foo**bar` is not emphasized
        //  - `**foo**bar` is emphasized
        if (
          (
            ($closer->getFlags() & self::IS_START)
            || ($item->getFlags() & self::IS_END)
          )
          && (
            (Str\length($closer->getText()) + Str\length($item->getText()))
            % 3 === 0
          )
        ) {
          continue;
        }
        $opener = $item;
        break;
      }
      $opener_idx = $i;

      if ($opener === null) {
        $openers_bottom[$char][$closer_len] = $position - 1;
        if (!($closer_flags & self::IS_START)) {
          $stack[$closer_idx] = new Stack\TextNode(
            $closer_text,
            $closer->getStartOffset(),
            $closer->getEndOffset(),
          );
        }
        ++$position;
        continue;
      }

      // Have an opener and closer pair

      $opener_text = $opener->getText();
      $strong = Str\length($opener_text) >= 2 && Str\length($closer_text) >= 2;

      $chomp = $strong ? 2 : 1;
      $opener_text = Str\slice($opener_text, $chomp);
      $closer_text = Str\slice($closer_text, $chomp);

      // We're going to throw away the existing opener, closer, and everything
      // in between; we build up the replacements here.
      $mid_nodes = vec[];

      if ($opener_text !== '') {
        // Remove the chars from the end of the delimiter as
        // `**foo*` is `*<em>foo</em>`, not `<em>*foo</em>`
        $mid_nodes[] = new Stack\DelimiterNode(
          $opener_text,
          $opener->getFlags(),
          $opener->getStartOffset(),
          $opener->getEndOffset() - $chomp,
        );
      } else {
        // We just ate up the last of this delimiter, so there's now none.
        // Adjust the stack offset, as we're effectively removing something
        // earlier in the stack than the current position
        $position--;
      }

      $first_content_idx = $opener_idx + 1;
      $last_content_idx = $closer_idx - 1;
      $content_length = ($last_content_idx - $first_content_idx) + 1;

      $mid_nodes[] =
        Vec\slice($stack, $first_content_idx, $content_length)
        |> self::consumeStackSlice($context, $$)
        |> new self($strong, $$)
        |> new Stack\EmphasisNode(
          $$,
          $opener->getEndOffset() - $chomp,
          $closer->getStartOffset() + $chomp,
        );
      $position -= $content_length;

      if ($closer_text !== '') {
        // Same as openers, however we take it from the start, as
        // `*foo**` is `<em>foo</em>*`, not `<em>foo*</em>`
        $mid_nodes[] = new Stack\DelimiterNode(
          $closer_text,
          $closer->getFlags(),
          $closer->getStartOffset() + $chomp,
          $closer->getEndOffset(),
        );
      }

      $stack = Vec\concat(
        Vec\take($stack, $opener_idx),
        $mid_nodes,
        Vec\drop($stack, $closer_idx + 1),
      );
    }

    return $stack;
  }