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