xref: /dokuwiki/inc/Parsing/Parser.php (revision b73ece99c18919754d993a1d1f5cb27140555705)
1<?php
2
3namespace dokuwiki\Parsing;
4
5use dokuwiki\Debug\DebugHelper;
6use dokuwiki\Parsing\Lexer\Lexer;
7use dokuwiki\Parsing\ParserMode\AbstractMode;
8use dokuwiki\Parsing\ParserMode\Base;
9
10/**
11 * Sets up the Lexer with modes and points it to the Handler
12 * For an intro to the Lexer see: wiki:parser
13 */
14class Parser
15{
16    /** @var Handler */
17    protected $handler;
18
19    /** @var ModeRegistry the registry of the parse this parser belongs to */
20    protected ModeRegistry $registry;
21
22    /** @var Lexer $lexer */
23    protected $lexer;
24
25    /** @var AbstractMode[] $modes */
26    protected $modes = [];
27
28    /** @var bool mode connections may only be set up once */
29    protected $connected = false;
30
31    /**
32     * @param Handler $handler
33     * @param ModeRegistry $registry the registry of the parse, injected into
34     *     every mode (all descend from AbstractMode) as it is added
35     */
36    public function __construct(Handler $handler, ModeRegistry $registry)
37    {
38        $this->handler = $handler;
39        $this->registry = $registry;
40    }
41
42    /**
43     * Accessor for the Handler instance.
44     *
45     * @return Handler
46     */
47    public function getHandler()
48    {
49        return $this->handler;
50    }
51
52    /**
53     * Adds the base mode and initialized the lexer
54     *
55     * @param Base $BaseMode
56     */
57    protected function addBaseMode($BaseMode)
58    {
59        $this->modes['base'] = $BaseMode;
60        if (!$this->lexer) {
61            $this->lexer = new Lexer($this->handler, 'base', true);
62        }
63        $this->injectDependencies($BaseMode);
64        $this->handler->registerModeObject('base', $BaseMode);
65    }
66
67    /**
68     * Give a mode the shared objects of this parse: its registry and lexer.
69     *
70     * Called as the mode joins the parser, before any connect/handle callback
71     * runs. Every mode descends from AbstractMode, which carries the setters.
72     *
73     * @param AbstractMode $Mode
74     */
75    protected function injectDependencies(AbstractMode $Mode)
76    {
77        $Mode->setModeRegistry($this->registry);
78        $Mode->setLexer($this->lexer);
79    }
80
81    /**
82     * Add a new syntax element (mode) to the parser
83     *
84     * PHP preserves order of associative elements
85     * Mode sequence is important
86     *
87     * @param string $name
88     * @param AbstractMode $Mode
89     */
90    public function addMode($name, AbstractMode $Mode)
91    {
92        if (!isset($this->modes['base'])) {
93            $this->addBaseMode(new Base());
94        }
95        $this->injectDependencies($Mode);
96        $this->modes[$name] = $Mode;
97        $this->handler->registerModeObject($name, $Mode);
98    }
99
100    /**
101     * Connect all modes with each other
102     *
103     * This is the last step before actually parsing.
104     */
105    protected function connectModes()
106    {
107
108        if ($this->connected) {
109            return;
110        }
111
112        // Run all preConnect() first so modes can register shared
113        // metadata (e.g. line start markers) before any patterns are built
114        foreach (array_keys($this->modes) as $mode) {
115            if ($mode == 'base') continue;
116            $this->modes[$mode]->preConnect();
117        }
118
119        foreach (array_keys($this->modes) as $mode) {
120            if ($mode == 'base') continue;
121
122            foreach (array_keys($this->modes) as $cm) {
123                if ($this->modes[$cm]->accepts($mode)) {
124                    $this->modes[$mode]->connectTo($cm);
125                }
126            }
127
128            $this->modes[$mode]->postConnect();
129        }
130
131        $this->connected = true;
132    }
133
134    /**
135     * Parses wiki syntax to instructions
136     *
137     * @param string $doc the wiki syntax text
138     * @return array instructions
139     */
140    public function parse($doc)
141    {
142        $this->connectModes();
143        // Normalize CRs and pad doc
144        $doc = "\n" . str_replace("\r\n", "\n", $doc) . "\n";
145        $this->lexer->parse($doc);
146
147        if (!method_exists($this->handler, 'finalize')) {
148            /** @deprecated 2019-10 we have a legacy handler from a plugin, assume legacy _finalize exists */
149
150            DebugHelper::dbgCustomDeprecationEvent(
151                'finalize()',
152                $this->handler::class . '::_finalize()',
153                __METHOD__,
154                __FILE__,
155                __LINE__
156            );
157            $this->handler->_finalize();
158        } else {
159            $this->handler->finalize();
160        }
161        return $this->handler->calls;
162    }
163}
164