| | 1 | | namespace MoreStructures.SuffixTrees.Builders.Ukkonen; |
| | 2 | |
|
| | 3 | | /// <summary> |
| | 4 | | /// An object which keeps the state of execution of the Ukkonen algorithm, and enforce its coherency. |
| | 5 | | /// </summary> |
| | 6 | | internal class IterationState |
| | 7 | | { |
| | 8 | | /// <summary> |
| | 9 | | /// Builds a fresh <see cref="IterationState"/> object, to execute the Ukkonen algorithm on the provided |
| | 10 | | /// <paramref name="text"/>. |
| | 11 | | /// </summary> |
| | 12 | | /// <param name="text">The text, to build the Suffix Tree of.</param> |
| | 13 | | /// <remarks> |
| | 14 | | /// <para id="algorithm"> |
| | 15 | | /// ALGORITHM |
| | 16 | | /// <br/> |
| | 17 | | /// - Creates a <see cref="Root"/> object, which will be used as root for the entire execution of the |
| | 18 | | /// algorithm. |
| | 19 | | /// <br/> |
| | 20 | | /// - The <see cref="Root"/> node is also set as current <see cref="ActiveNode"/>, while |
| | 21 | | /// <see cref="ActiveEdgeStart"/> is set to a negative value and <see cref="ActiveLength"/> to 0 (as there |
| | 22 | | /// is no a valid <see cref="ActiveEdge"/> yet - it is going to be set when Rule 3 is triggered). |
| | 23 | | /// <br/> |
| | 24 | | /// - <see cref="Phase"/> is initially set to a negative value: the first phase has to be explicitely |
| | 25 | | /// started with <see cref="StartPhaseIncreasingRemainingAndGlobalEnd"/>. |
| | 26 | | /// </para> |
| | 27 | | /// </remarks> |
| 57 | 28 | | public IterationState(TextWithTerminator text) |
| 57 | 29 | | { |
| 57 | 30 | | NextLeafStart = 0; |
| 57 | 31 | | NextNodeId = 0; |
| | 32 | |
|
| 57 | 33 | | Text = text; |
| 57 | 34 | | Root = new(NextNodeId++, null, null, null); // The root is never a leaf and has no incoming edge |
| | 35 | |
|
| 57 | 36 | | Phase = -1; |
| 57 | 37 | | RemainingSuffixes = 0; |
| 57 | 38 | | PreviousInternalNodeInTheSamePhase = null; |
| 57 | 39 | | GlobalEnd = new(-1); |
| | 40 | |
|
| 57 | 41 | | ActiveNode = Root; |
| 57 | 42 | | ActiveEdgeStart = -1; |
| 57 | 43 | | ActiveLength = 0; |
| 57 | 44 | | } |
| | 45 | |
|
| | 46 | | #region General readonly properties |
| | 47 | |
|
| | 48 | | /// <summary> |
| | 49 | | /// The <see cref="TextWithTerminator"/> to build the <see cref="SuffixTreeNode"/> structure of. |
| | 50 | | /// </summary> |
| 6696 | 51 | | public TextWithTerminator Text { get; } |
| | 52 | |
|
| | 53 | | /// <summary> |
| | 54 | | /// The root node of the Suffix Tree, temporarily used during tree construction. |
| | 55 | | /// </summary> |
| 1265 | 56 | | public MutableNode Root { get; } |
| | 57 | |
|
| | 58 | | #endregion |
| | 59 | |
|
| | 60 | | #region Cursors |
| | 61 | |
|
| | 62 | | /// <summary> |
| | 63 | | /// The top-level iteration. There are as many phases as chars in <see cref="Text"/>. |
| | 64 | | /// </summary> |
| | 65 | | private int Phase; |
| | 66 | |
|
| | 67 | | /// <summary> |
| | 68 | | /// How many suffixes still need to be created within the current phase. |
| | 69 | | /// </summary> |
| | 70 | | /// <remarks> |
| | 71 | | /// Every time a leaf is created, a suffix has been fully processed, so <see cref="RemainingSuffixes"/> is reduced |
| | 72 | | /// by one, whereas when the active edge and length are changed, because RULE 3 EXTENSION is trigger, the tree is |
| | 73 | | /// in implicit state, i.e. there is no edge with termination character and no internal node with only one edge |
| | 74 | | /// going out of it. |
| | 75 | | /// </remarks> |
| | 76 | | private int RemainingSuffixes; |
| | 77 | |
|
| | 78 | | /// <summary> |
| | 79 | | /// The internal node created in the previous iteration of the same phase, or <see langword="null"/>, if |
| | 80 | | /// there hasn't been a previous iteration in the same phase where an internal node has been created. |
| | 81 | | /// </summary> |
| | 82 | | private MutableNode? PreviousInternalNodeInTheSamePhase; |
| | 83 | |
|
| | 84 | | /// <summary> |
| | 85 | | /// The end of the text, which is a moving target - it gets increased every time a new char from |
| | 86 | | /// <see cref="Text"/> is processed. |
| | 87 | | /// </summary> |
| | 88 | | /// <remarks> |
| | 89 | | /// Since it is kept by reference from edges, increasing ends at |
| | 90 | | /// <see cref="StartPhaseIncreasingRemainingAndGlobalEnd"/> performs the RULE 1 EXTENSION on all leafs at no |
| | 91 | | /// additional cost. |
| | 92 | | /// <br/> |
| | 93 | | /// This is one of the mechanisms that make time Ukkonen algorithm complexity linear. |
| | 94 | | /// </remarks> |
| | 95 | | private readonly MovingEnd GlobalEnd; |
| | 96 | |
|
| | 97 | | private int NextLeafStart; |
| | 98 | | private int NextNodeId; |
| | 99 | |
|
| | 100 | | /// <summary> |
| | 101 | | /// The char in <see cref="Text"/> at index <see cref="Phase"/>. |
| | 102 | | /// </summary> |
| | 103 | | private char CurrentChar |
| | 104 | | { |
| | 105 | | get |
| 1346 | 106 | | { |
| 1346 | 107 | | if (Phase < 0) |
| 1 | 108 | | throw new InvalidOperationException("No phase started yet"); |
| 1345 | 109 | | return Text[Phase]; |
| 1345 | 110 | | } |
| | 111 | | } |
| | 112 | |
|
| | 113 | | #endregion |
| | 114 | |
|
| | 115 | | #region Active Point |
| | 116 | |
|
| | 117 | | /// <summary> |
| | 118 | | /// The node in the tree currently being built, where to start from when looking for <see cref="CurrentChar"/>. |
| | 119 | | /// </summary> |
| | 120 | | /// <remarks> |
| | 121 | | /// At the beginning it is set to the root of the about-to-be-built tree. The root doesn't have a Suffix Link. |
| | 122 | | /// Unlike <see cref="ActiveEdge"/> and <see cref="ActiveLength"/>, <see cref="ActiveNode"/> is always defined. |
| | 123 | | /// <br/> |
| | 124 | | /// It's changed by <see cref="JumpActivePointIfNecessary"/>, when the current <see cref="ActiveEdge"/> has been |
| | 125 | | /// fully traversed. |
| | 126 | | /// <br/> |
| | 127 | | /// It is also changed by <see cref="CreateLeafAndPossiblyIntermediateAndDecrementRemainingSuffixes"/> in Rule |
| | 128 | | /// 2 Extension, and set to its <see cref="MutableNode.SuffixLink"/>, when an internal node has been created |
| | 129 | | /// and the <see cref="ActiveNode"/> is not the <see cref="Root"/> node. |
| | 130 | | /// </remarks> |
| | 131 | | private MutableNode ActiveNode; |
| | 132 | |
|
| | 133 | | /// <summary> |
| | 134 | | /// The index in the text, of the 1st char identifying the edge starting from <see cref="ActiveNode"/>, where |
| | 135 | | /// to start from when looking for <see cref="CurrentChar"/>. It's -1 if no edge has been selected yet. |
| | 136 | | /// </summary> |
| | 137 | | /// <remarks> |
| | 138 | | /// For example, if the edge is labelled as "abc" and the 1st "a" corresponds to the 1st char of the text |
| | 139 | | /// "abcxyx$", the active edge is 0. At the beginning no edge has been selected from the active node, so |
| | 140 | | /// there is no active edge, and the value -1 is set. In this scenario <see cref="ActiveLength"/> is also |
| | 141 | | /// reset to 0. |
| | 142 | | /// </remarks> |
| | 143 | | private int ActiveEdgeStart; |
| | 144 | |
|
| | 145 | | /// <summary> |
| | 146 | | /// The amount of chars which have been processed on the <see cref="ActiveEdge"/>. |
| | 147 | | /// </summary> |
| | 148 | | /// <remarks> |
| | 149 | | /// At the beginning, no edge has been selected from the active node, so its value is 0. |
| | 150 | | /// </remarks> |
| | 151 | | private int ActiveLength; |
| | 152 | |
|
| | 153 | | /// <summary> |
| | 154 | | /// The edge coming out of <see cref="ActiveNode"/> which starts with <see cref="ActiveEdgeStart"/>. |
| | 155 | | /// </summary> |
| | 156 | | /// <exception cref="InvalidOperationException"> |
| | 157 | | /// If <see cref="ActiveEdgeStart"/> is invalid in the current context (i.e. there is no active point). |
| | 158 | | /// </exception> |
| | 159 | | private MutableEdge ActiveEdge |
| | 160 | | { |
| | 161 | | get |
| 700 | 162 | | { |
| 700 | 163 | | var activeEdgeOrDefault = ActiveNode.Children.Keys.SingleOrDefault( |
| 2473 | 164 | | edge => Text[edge.Start] == Text[ActiveEdgeStart]); |
| 700 | 165 | | if (activeEdgeOrDefault is not MutableEdge activeEdge) |
| 1 | 166 | | throw new InvalidOperationException($"No edge from {ActiveNode} starting with {ActiveEdgeStart}"); |
| 699 | 167 | | return activeEdge; |
| 699 | 168 | | } |
| | 169 | | } |
| | 170 | |
|
| | 171 | | #endregion |
| | 172 | |
|
| | 173 | | /// <summary> |
| | 174 | | /// Checks whether there are still more top-level iterations to execute, or not. |
| | 175 | | /// </summary> |
| | 176 | | /// <returns>True if there are still phases to go, false otherwise.</returns> |
| 394 | 177 | | public bool IsThereANextPhase() => Phase < Text.Length - 1; |
| | 178 | |
|
| | 179 | | /// <summary> |
| | 180 | | /// Start a new phase, i.e. to the next char in <see cref="Text"/>, increasing <see cref="RemainingSuffixes"/> |
| | 181 | | /// and <see cref="GlobalEnd"/> and resetting <see cref="PreviousInternalNodeInTheSamePhase"/>. |
| | 182 | | /// </summary> |
| | 183 | | public void StartPhaseIncreasingRemainingAndGlobalEnd() |
| 355 | 184 | | { |
| 355 | 185 | | Phase++; |
| 355 | 186 | | RemainingSuffixes++; |
| 355 | 187 | | GlobalEnd.Value++; |
| 355 | 188 | | PreviousInternalNodeInTheSamePhase = null; |
| 355 | 189 | | } |
| | 190 | |
|
| | 191 | | /// <summary> |
| | 192 | | /// Whether there still are suffixes to be processed in the current <see cref="Phase"/>. |
| | 193 | | /// </summary> |
| | 194 | | /// <returns>True if there are, false otherwise.</returns> |
| | 195 | | public bool StillRemainingSuffixesInCurrentPhase() |
| 685 | 196 | | { |
| 685 | 197 | | if (Phase < 0) |
| 1 | 198 | | throw new InvalidOperationException("No phase started yet"); |
| 684 | 199 | | return RemainingSuffixes > 0; |
| 684 | 200 | | } |
| | 201 | |
|
| | 202 | | /// <summary> |
| | 203 | | /// If no active point is currently defined (non-positive <see cref="ActiveLength"/>), and there is an |
| | 204 | | /// edge coming out of the <see cref="ActiveNode"/> which starts with <see cref="CurrentChar"/>, gives back |
| | 205 | | /// that edge. |
| | 206 | | /// </summary> |
| | 207 | | /// <returns>The edge, if found. Otherwise null.</returns> |
| | 208 | | public MutableEdge? NoActivePointAndEdgeStartingFromActiveNodeWithCurrentChar() |
| 496 | 209 | | { |
| 496 | 210 | | if (ActiveLength > 0) |
| 163 | 211 | | return null; |
| | 212 | |
|
| 1012 | 213 | | return ActiveNode.Children.Keys.SingleOrDefault(edge => Text[edge.Start] == CurrentChar); |
| 496 | 214 | | } |
| | 215 | |
|
| | 216 | | /// <summary> |
| | 217 | | /// Sets the <see cref="ActiveEdge"/>, by actually storing its <see cref="ActiveEdgeStart"/>. It also set |
| | 218 | | /// <see cref="ActiveLength"/> to 1, since activating an edge means consuming the 1st char of the edge, as it |
| | 219 | | /// corresponds to the current char of <see cref="Text"/>. |
| | 220 | | /// </summary> |
| | 221 | | /// <param name="edge">The new <see cref="ActiveEdge"/> to set.</param> |
| | 222 | | public void InitializeActiveEdgeAndLength(MutableEdge edge) |
| 116 | 223 | | { |
| 116 | 224 | | ActiveEdgeStart = edge.Start; |
| 116 | 225 | | ActiveLength = 1; |
| | 226 | |
|
| 116 | 227 | | JumpActivePointIfNecessary(edge); |
| 115 | 228 | | } |
| | 229 | |
|
| | 230 | | /// <summary> |
| | 231 | | /// Whether there is an active point (active node + edge + length), followed by the <see cref="CurrentChar"/> |
| | 232 | | /// being processed in the current <see cref="Phase"/>. |
| | 233 | | /// </summary> |
| | 234 | | /// <returns>True if there is an active point which respects the condition, false otherwise.</returns> |
| | 235 | | /// <exception cref="InvalidOperationException">If the active point state is inconsistent.</exception> |
| | 236 | | public bool ActivePointFollowedByCurrentChar() |
| 396 | 237 | | { |
| 396 | 238 | | if (ActiveEdgeStart < 0) |
| 225 | 239 | | return false; |
| | 240 | |
|
| 171 | 241 | | return Text[ActiveEdge.Start + ActiveLength] == CurrentChar; |
| 395 | 242 | | } |
| | 243 | |
|
| | 244 | | /// <summary> |
| | 245 | | /// Increment <see cref="ActiveLength"/>, updating the Active Point if necessary. |
| | 246 | | /// Used when <see cref="ActivePointFollowedByCurrentChar"/> is satified, to trigger Rule 3 Extension, which is |
| | 247 | | /// a showstopper for the current phase. |
| | 248 | | /// </summary> |
| | 249 | | public void IncrementActiveLength() |
| 47 | 250 | | { |
| 47 | 251 | | ActiveLength++; |
| 47 | 252 | | JumpActivePointIfNecessary(ActiveEdge); |
| 47 | 253 | | } |
| | 254 | |
|
| | 255 | | /// <summary> |
| | 256 | | /// Makes the active point properties consistent with each other, jumping <see cref="ActiveNode"/> if necessary |
| | 257 | | /// and resetting <see cref="ActiveEdgeStart"/> and <see cref="ActiveLength"/> when appropriate. |
| | 258 | | /// </summary> |
| | 259 | | /// <remarks> |
| | 260 | | /// <para id="algo"> |
| | 261 | | /// ALGORITHM |
| | 262 | | /// <br/> |
| | 263 | | /// Performed at the end of every iteration. |
| | 264 | | /// <br/> |
| | 265 | | /// - If there is no Active Point, i.e. negative <see cref="ActiveEdgeStart"/> or zero |
| | 266 | | /// <see cref="ActiveLength"/>, there's nothing to update. Therefore, exit. |
| | 267 | | /// <br/> |
| | 268 | | /// - Scenario 1: If there are still chars not traversed on the current <see cref="ActiveEdge"/>, the Active |
| | 269 | | /// Point is still valid. Therefore, exit. Otherwise, perform the update. |
| | 270 | | /// <br/> |
| | 271 | | /// <br/> |
| | 272 | | /// There are two scenarios when all chars have been consumed on the current Active Edge, and an update of the |
| | 273 | | /// Active Point is required. |
| | 274 | | /// <br/> |
| | 275 | | /// - Scenario 2: the Active Point points to the last char of the <see cref="ActiveEdge"/>. This can happen |
| | 276 | | /// when Rule 3 Extension is applied and <see cref="ActiveLength"/> is increased by 1, reaching the end of |
| | 277 | | /// the current <see cref="ActiveEdge"/>. In this scenario, change <see cref="ActiveNode"/>, jumping to the |
| | 278 | | /// node pointed by the current <see cref="ActiveEdge"/>, and reset the Active Point. |
| | 279 | | /// <br/> |
| | 280 | | /// - Scenario 3: the Active Point overflows, with the ActiveLength going beyond the <see cref="ActiveEdge"/>. |
| | 281 | | /// This can happen when Rule 2 Extension is applied and <see cref="ActiveEdgeStart"/> is increased by 1, |
| | 282 | | /// changing active edge, and <see cref="ActiveLength"/> is decreased by 1, but the edge which was unique on |
| | 283 | | /// the previous branch is split into a sequence of edges on the new branch. In this scenario, change |
| | 284 | | /// <see cref="ActiveNode"/>, jumping to the node pointed by the current <see cref="ActiveEdge"/>, and set |
| | 285 | | /// the Active Point accordingly. |
| | 286 | | /// </para> |
| | 287 | | /// </remarks> |
| | 288 | | public void JumpActivePointIfNecessary(MutableEdge? previousActiveEdge) |
| 505 | 289 | | { |
| 519 | 290 | | while (true) |
| 519 | 291 | | { |
| 519 | 292 | | if (ActiveEdgeStart < 0 || ActiveLength <= 0) |
| 277 | 293 | | { |
| 277 | 294 | | ActiveEdgeStart = -1; |
| 277 | 295 | | ActiveLength = 0; |
| 277 | 296 | | return; |
| | 297 | | } |
| | 298 | |
|
| 242 | 299 | | var currentActiveEdge = ActiveEdge; |
| 241 | 300 | | var remainingCharsOnActiveEdgeAfterActivePoint = |
| 241 | 301 | | currentActiveEdge.End.Value - (currentActiveEdge.Start + ActiveLength - 1); |
| | 302 | |
|
| | 303 | | // Scenario 1 |
| 241 | 304 | | if (remainingCharsOnActiveEdgeAfterActivePoint > 0) |
| 170 | 305 | | { |
| 170 | 306 | | return; |
| | 307 | | } |
| | 308 | |
|
| | 309 | | // Scenario 2 |
| 71 | 310 | | if (remainingCharsOnActiveEdgeAfterActivePoint == 0) |
| 57 | 311 | | { |
| 57 | 312 | | ActiveNode = ActiveNode.Children[currentActiveEdge]; |
| 57 | 313 | | ActiveEdgeStart = -1; |
| 57 | 314 | | ActiveLength = 0; |
| 57 | 315 | | return; |
| | 316 | | } |
| | 317 | |
|
| | 318 | | // Scenario 3 |
| 14 | 319 | | var firstCharOverflowingCurrentActiveEdge = previousActiveEdge!.Start + currentActiveEdge.Length; |
| 14 | 320 | | ActiveNode = ActiveNode.Children[currentActiveEdge]; |
| 14 | 321 | | ActiveEdgeStart = ActiveNode.Children.Keys.Single( |
| 46 | 322 | | edge => Text[edge.Start] == Text[firstCharOverflowingCurrentActiveEdge]).Start; |
| 14 | 323 | | ActiveLength = -remainingCharsOnActiveEdgeAfterActivePoint; |
| | 324 | |
|
| 14 | 325 | | previousActiveEdge = currentActiveEdge; |
| 14 | 326 | | } |
| 504 | 327 | | } |
| | 328 | |
|
| | 329 | | /// <summary> |
| | 330 | | /// Performs Rule 2 Extension. |
| | 331 | | /// </summary> |
| | 332 | | public void CreateLeafAndPossiblyIntermediateAndDecrementRemainingSuffixes() |
| 342 | 333 | | { |
| 342 | 334 | | var leafEdge = new MutableEdge(Phase, GlobalEnd); |
| 342 | 335 | | var leafNode = new MutableNode(NextNodeId++, NextLeafStart++, null, leafEdge); // Leaves don't have Suffix Link |
| | 336 | |
|
| | 337 | | MutableNode internalNode; |
| | 338 | |
|
| 342 | 339 | | if (ActiveLength == 0) |
| 222 | 340 | | { |
| | 341 | | // No current active edge => branch out from the active node directly |
| 222 | 342 | | ActiveNode.Children[leafEdge] = leafNode; |
| | 343 | |
|
| | 344 | | // Stop edge to ActiveNode from tracking the end of the text, as ActiveNode transitions from being a leaf |
| | 345 | | // to an intermediate node |
| 222 | 346 | | if (ActiveNode.IncomingEdge != null) // False on the root |
| 34 | 347 | | { |
| 34 | 348 | | ActiveNode.IncomingEdge.End = new(ActiveNode.IncomingEdge.End.Value); |
| 34 | 349 | | } |
| | 350 | |
|
| 222 | 351 | | internalNode = ActiveNode; |
| 222 | 352 | | } |
| | 353 | | else |
| 120 | 354 | | { |
| | 355 | | // There is an active edge => split the active edge into two, with an intern node in the middle |
| 120 | 356 | | var activeEdge = ActiveEdge; |
| 120 | 357 | | MutableEdge sharedEdge = new(activeEdge.Start, new(activeEdge.Start + ActiveLength - 1)); |
| 120 | 358 | | MutableEdge reminderEdge = new(activeEdge.Start + ActiveLength, activeEdge.End); |
| | 359 | |
|
| 120 | 360 | | internalNode = new(NextNodeId++, null, Root, sharedEdge); |
| 120 | 361 | | internalNode.Children[reminderEdge] = ActiveNode.Children[activeEdge]; |
| 120 | 362 | | internalNode.Children[leafEdge] = leafNode; |
| | 363 | |
|
| 120 | 364 | | ActiveNode.Children[sharedEdge] = internalNode; |
| 120 | 365 | | ActiveNode.Children.Remove(activeEdge); |
| 120 | 366 | | } |
| | 367 | |
|
| | 368 | | // If an internal node has been created in a previous Rule 2 Extension of the current phase, update |
| | 369 | | // the SuffixLink of the previous internal node to be the just created internal node, and store the |
| | 370 | | // just create internal store PreviousInternalNodeInTheSamePhase, to be able to set the Suffix Link |
| | 371 | | // on next internal node if a new Rule 2 Extension happens after this one, in the same phase |
| 342 | 372 | | if (PreviousInternalNodeInTheSamePhase is MutableNode previousInternalNode) |
| 129 | 373 | | previousInternalNode.SuffixLink = internalNode; |
| 342 | 374 | | PreviousInternalNodeInTheSamePhase = internalNode; |
| | 375 | |
|
| 342 | 376 | | var previousActiveEdge = ActiveEdgeStart >= 0 ? ActiveEdge : null; |
| | 377 | |
|
| | 378 | | // Now that the leaf x has been created, and possibly the internal node abc..z (length = RemainingSuffixes) |
| | 379 | | // has also been created, and the previous internal node updated (if any), there are still RemainingSuffixes |
| | 380 | | // suffixes to be processed: bc..zx, c..zx, ... x. Those can be easily found in the tree by using Suffix Links. |
| 342 | 381 | | if (ActiveNode == Root && ActiveEdgeStart >= 0) |
| 82 | 382 | | { |
| | 383 | | // To move from abc..zx to bc..zx when the active node is the root, increment ActiveEdgeStart |
| | 384 | | // (which means that the start of the new ActiveEdge is now b) and decrement ActiveLength (because |
| | 385 | | // now the ActiveEdge is one char shorter. |
| | 386 | | // The edge bc..z is guaranteed to exist already because bc..z it's a suffix already processed in |
| | 387 | | // previous iterations and Rule 1 Extension implicitely extend bc..z to bc..zx. |
| 82 | 388 | | ActiveEdgeStart++; |
| 82 | 389 | | ActiveLength--; |
| 82 | 390 | | } |
| | 391 | | else |
| 260 | 392 | | { |
| | 393 | | // To move from abc..zx to bc..zx when the active node is an internal node N, move the active node |
| | 394 | | // to the Suffix Link of N, which is by definition the node having path bc..zx. Active edge and |
| | 395 | | // length don't change, because the new active node has the suffix bc..zx. |
| 260 | 396 | | ActiveNode = ActiveNode.SuffixLink ?? Root; |
| 260 | 397 | | } |
| | 398 | |
|
| 342 | 399 | | JumpActivePointIfNecessary(previousActiveEdge); |
| | 400 | |
|
| | 401 | | // Because a leaf has been created, a suffix has been processed, and the number of remaining suffixes |
| | 402 | | // (which is > 1 in case previous phases have terminated with a showstopper due to Rule 3 Extension) |
| | 403 | | // is decreased by 1. |
| 342 | 404 | | RemainingSuffixes--; |
| 342 | 405 | | } |
| | 406 | |
|
| | 407 | | /// <inheritdoc path="//*[not(self::summary)]"/> |
| | 408 | | /// <summary> |
| | 409 | | /// <inheritdoc/> |
| | 410 | | /// <br/> |
| | 411 | | /// Visualization of the main state variables, such as <see cref="Phase"/>, <see cref="RemainingSuffixes"/> |
| | 412 | | /// and active point data. |
| | 413 | | /// </summary> |
| | 414 | | public override string ToString() => |
| 496 | 415 | | string.Join(", ", |
| 496 | 416 | | $"{nameof(Phase)} = {Phase,-2}", |
| 496 | 417 | | $"{nameof(CurrentChar)} = {CurrentChar}", |
| 496 | 418 | | $"{nameof(RemainingSuffixes)} = {RemainingSuffixes,-2}", |
| 496 | 419 | | $"{nameof(ActiveNode)} = {ActiveNode,-2}", |
| 496 | 420 | | $"{nameof(ActiveEdgeStart)} = {ActiveEdgeStart,-2}", |
| 496 | 421 | | $"{nameof(ActiveLength)} = {ActiveLength,-2}") + Environment.NewLine + |
| 496 | 422 | | $"Tree = {Root.Dump(new(Text, GlobalEnd))}"; |
| | 423 | | } |