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