xref: /dokuwiki/inc/Parsing/ParserMode/GfmEmphasis.php (revision 3a56ec9bc396c50c9d879450c0f5baf168a75b06)
1<?php
2
3namespace dokuwiki\Parsing\ParserMode;
4
5/**
6 * GFM / CommonMark emphasis via single asterisks: `*text*`.
7 *
8 * Emits emphasis_open / emphasis_close — the same instructions as DokuWiki's
9 * Emphasis (`//`), so both syntaxes render as <em>.
10 */
11class GfmEmphasis extends AbstractFormatting
12{
13    /** @inheritdoc */
14    public function getSort()
15    {
16        return 80;
17    }
18
19    /** @inheritdoc */
20    protected function getModeName(): string
21    {
22        return 'gfm_emphasis';
23    }
24
25    /** @inheritdoc */
26    protected function getInstructionName(): string
27    {
28        return 'emphasis';
29    }
30
31    /** @inheritdoc */
32    protected function getEntryPattern(): string
33    {
34        // Broken down:
35        //   \*                        — opening `*`
36        //   (?=                       — lookahead: a valid closer must exist
37        //     [^\s*]                  —   first body char: not whitespace, not `*`
38        //                                 (flanking-opener rule)
39        //     (?:NOT_AT_PARA_BREAK    —   possessive run of any non-`*` char
40        //        [^*])*+              —     that doesn't start a paragraph break
41        //     (?<![\s*])              —   last body char: not whitespace, not `*`
42        //                                 (flanking-closer rule)
43        //     \*                      —   closing `*`
44        //   )
45        // The lookahead only reaches the nearest `*` (its body is `[^*]`), so
46        // it enforces CommonMark's nearest-delimiter pairing that a plain
47        // closer scan cannot express. The inherited closer check additionally
48        // keeps the opener from spanning an enclosing mode's closer (e.g. a
49        // stray `*` inside ''glob/*.conf'').
50        //
51        // The body run is possessive and the flanking-closer rule is a
52        // lookbehind rather than a trailing character the run must give back:
53        // `[^*]` cannot match `*`, so the run always stops at the nearest `*`
54        // (or a paragraph break) and never needs to backtrack. A plain
55        // quantifier with a trailing `[^\s*]` forced backtracking on every
56        // opener, so a long run of `*` with no valid closer made the non-JIT
57        // PCRE engine retain one backtracking frame per byte.
58        return '\*(?=[^\s*](?:' . self::NOT_AT_PARA_BREAK . '[^*])*+(?<![\s*])\*)';
59    }
60
61    /** @inheritdoc */
62    protected function getExitPattern(): string
63    {
64        return '(?<=[^\s])\*';
65    }
66}
67