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