mirror of
https://github.com/xoofx/markdig.git
synced 2026-10-08 22:57:51 +00:00
795 lines
27 KiB
C#
795 lines
27 KiB
C#
// Copyright (c) Alexandre Mutel. All rights reserved.
|
|
// This file is licensed under the BSD-Clause 2 license.
|
|
// See the license.txt file in the project root for more information.
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using Textamina.Markdig.Helpers;
|
|
using Textamina.Markdig.Syntax;
|
|
|
|
namespace Textamina.Markdig.Parsers
|
|
{
|
|
/// <summary>
|
|
/// The block processor.
|
|
/// </summary>
|
|
public class BlockProcessor
|
|
{
|
|
private BlockProcessor root;
|
|
private int currentStackIndex;
|
|
private readonly BlockParserStateCache parserStateCache;
|
|
private int originalLineStart = 0;
|
|
|
|
private BlockProcessor(BlockProcessor root)
|
|
{
|
|
// These properties are not changing between a parent and a children BlockProcessor
|
|
this.root = root;
|
|
this.parserStateCache = root.parserStateCache;
|
|
StringBuilders = root.StringBuilders;
|
|
Document = root.Document;
|
|
Parsers = root.Parsers;
|
|
|
|
// These properties are local to a state
|
|
OpenedBlocks = new List<Block>();
|
|
NewBlocks = new Stack<Block>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BlockProcessor" /> class.
|
|
/// </summary>
|
|
/// <param name="stringBuilders">The string builders cache.</param>
|
|
/// <param name="document">The document to build blocks into.</param>
|
|
/// <param name="parsers">The list of parsers.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// </exception>
|
|
public BlockProcessor(StringBuilderCache stringBuilders, MarkdownDocument document, BlockParserList parsers)
|
|
{
|
|
if (stringBuilders == null) throw new ArgumentNullException(nameof(stringBuilders));
|
|
if (document == null) throw new ArgumentNullException(nameof(document));
|
|
if (parsers == null) throw new ArgumentNullException(nameof(parsers));
|
|
parserStateCache = new BlockParserStateCache(this);
|
|
StringBuilders = stringBuilders;
|
|
Document = document;
|
|
document.IsOpen = true;
|
|
Parsers = parsers;
|
|
parsers.Initialize(this);
|
|
OpenedBlocks = new List<Block>();
|
|
NewBlocks = new Stack<Block>();
|
|
root = this;
|
|
Open(document);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the new blocks to push. A <see cref="BlockParser"/> is required to push new blocks that it creates to this property.
|
|
/// </summary>
|
|
public Stack<Block> NewBlocks { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the list of <see cref="BlockParser"/> configured with this parser state.
|
|
/// </summary>
|
|
public BlockParserList Parsers { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the current active container.
|
|
/// </summary>
|
|
public ContainerBlock CurrentContainer { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the last block that is opened.
|
|
/// </summary>
|
|
public Block CurrentBlock { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the last block that is created.
|
|
/// </summary>
|
|
public Block LastBlock { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the next block in a <see cref="BlockParser.TryContinue"/>.
|
|
/// </summary>
|
|
public Block NextContinue => currentStackIndex + 1 < OpenedBlocks.Count ? OpenedBlocks[currentStackIndex + 1] : null;
|
|
|
|
/// <summary>
|
|
/// Gets the root document.
|
|
/// </summary>
|
|
public MarkdownDocument Document { get; }
|
|
|
|
/// <summary>
|
|
/// The current line being processed.
|
|
/// </summary>
|
|
public StringSlice Line;
|
|
|
|
/// <summary>
|
|
/// Gets the index of the line in the source text.
|
|
/// </summary>
|
|
public int LineIndex { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the line is blank (valid only after <see cref="ParseIndent"/> has been called).
|
|
/// </summary>
|
|
public bool IsBlankLine => CurrentChar == '\0';
|
|
|
|
/// <summary>
|
|
/// Gets the current character being processed.
|
|
/// </summary>
|
|
public char CurrentChar => Line.CurrentChar;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the column.
|
|
/// </summary>
|
|
public int Column { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets the position of the current character in the line being processed.
|
|
/// </summary>
|
|
public int Start => Line.Start;
|
|
|
|
/// <summary>
|
|
/// Gets the current indent position (number of columns between the previous indent and the current position).
|
|
/// </summary>
|
|
public int Indent => Column - ColumnBeforeIndent;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether a code indentation is at the beginning of the line being processed.
|
|
/// </summary>
|
|
public bool IsCodeIndent => Indent >= 4;
|
|
|
|
/// <summary>
|
|
/// Gets the column position before the indent occured.
|
|
/// </summary>
|
|
public int ColumnBeforeIndent { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the character position before the indent occured.
|
|
/// </summary>
|
|
public int StartBeforeIndent { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the cache of string builders.
|
|
/// </summary>
|
|
public StringBuilderCache StringBuilders { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the current stack of <see cref="Block"/> being processed.
|
|
/// </summary>
|
|
private List<Block> OpenedBlocks { get; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether to continue processing the current line.
|
|
/// </summary>
|
|
private bool ContinueProcessingLine { get; set; }
|
|
|
|
/// <summary>
|
|
/// Returns the next character in the line being processed. Update <see cref="Start"/> and <see cref="Column"/>.
|
|
/// </summary>
|
|
/// <returns>The next character or `\0` if end of line is reached</returns>
|
|
public char NextChar()
|
|
{
|
|
var c = Line.CurrentChar;
|
|
if (c == '\t')
|
|
{
|
|
Column = CharHelper.AddTab(Column);
|
|
}
|
|
else
|
|
{
|
|
Column++;
|
|
}
|
|
return Line.NextChar();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the next character in the line taking into space taken by tabs. Update <see cref="Start"/> and <see cref="Column"/>.
|
|
/// </summary>
|
|
public void NextColumn()
|
|
{
|
|
var c = Line.CurrentChar;
|
|
// If we are accross a tab, we should just add 1 column
|
|
if (c == '\t' && CharHelper.IsAcrossTab(Column))
|
|
{
|
|
Column++;
|
|
}
|
|
else
|
|
{
|
|
Line.NextChar();
|
|
Column++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Peeks a character at the specified offset from the current position in the line.
|
|
/// </summary>
|
|
/// <param name="offset">The offset.</param>
|
|
/// <returns>A character peeked at the specified offset</returns>
|
|
public char PeekChar(int offset)
|
|
{
|
|
return Line.PeekChar(offset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restarts the indent from the current position.
|
|
/// </summary>
|
|
public void RestartIndent()
|
|
{
|
|
StartBeforeIndent = Start;
|
|
ColumnBeforeIndent = Column;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the indentation from the current position in the line, updating <see cref="StartBeforeIndent"/>,
|
|
/// <see cref="ColumnBeforeIndent"/>, <see cref="Start"/> and <see cref="Column"/> accordingly
|
|
/// taking into account space taken by tabs.
|
|
/// </summary>
|
|
public void ParseIndent()
|
|
{
|
|
var c = CurrentChar;
|
|
var previousStartBeforeIndent = StartBeforeIndent;
|
|
var startBeforeIndent = Start;
|
|
var previousColumnBeforeIndent = ColumnBeforeIndent;
|
|
var columnBeforeIndent = Column;
|
|
while (c !='\0')
|
|
{
|
|
if (c == '\t')
|
|
{
|
|
Column = CharHelper.AddTab(Column);
|
|
}
|
|
else if (c == ' ')
|
|
{
|
|
Column++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
c = Line.NextChar();
|
|
}
|
|
if (columnBeforeIndent == Column)
|
|
{
|
|
StartBeforeIndent = previousStartBeforeIndent;
|
|
ColumnBeforeIndent = previousColumnBeforeIndent;
|
|
}
|
|
else
|
|
{
|
|
StartBeforeIndent = startBeforeIndent;
|
|
ColumnBeforeIndent = columnBeforeIndent;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves to the position to the specified column position, taking into account spaces in tabs.
|
|
/// </summary>
|
|
/// <param name="newColumn">The new column position to move the cursor to.</param>
|
|
public void GoToColumn(int newColumn)
|
|
{
|
|
// Optimized path when we are moving above the previous start of indent
|
|
if (newColumn > ColumnBeforeIndent)
|
|
{
|
|
Line.Start = StartBeforeIndent;
|
|
Column = ColumnBeforeIndent;
|
|
ColumnBeforeIndent = 0;
|
|
StartBeforeIndent = 0;
|
|
}
|
|
else
|
|
{
|
|
Line.Start = originalLineStart;
|
|
Column = 0;
|
|
ColumnBeforeIndent = 0;
|
|
StartBeforeIndent = originalLineStart;
|
|
}
|
|
for (; Line.Start <= Line.End && Column < newColumn; Line.Start++)
|
|
{
|
|
var c = Line.Text[Line.Start];
|
|
if (c == '\t')
|
|
{
|
|
Column = CharHelper.AddTab(Column);
|
|
}
|
|
else
|
|
{
|
|
if (!c.IsSpaceOrTab())
|
|
{
|
|
ColumnBeforeIndent = Column + 1;
|
|
StartBeforeIndent = Line.Start + 1;
|
|
}
|
|
|
|
Column++;
|
|
}
|
|
}
|
|
if (Column > newColumn)
|
|
{
|
|
Column = newColumn;
|
|
if (Line.Start > 0)
|
|
{
|
|
Line.Start--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves to the position to the code indent (<see cref="ColumnBeforeIndent"/> + 4 spaces).
|
|
/// </summary>
|
|
/// <param name="columnOffset">The column offset to apply to this indent.</param>
|
|
public void GoToCodeIndent(int columnOffset = 0)
|
|
{
|
|
GoToColumn(ColumnBeforeIndent + 4 + columnOffset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the specified block.
|
|
/// </summary>
|
|
/// <param name="block">The block.</param>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.ArgumentException">The block must be opened</exception>
|
|
public void Open(Block block)
|
|
{
|
|
if (block == null) throw new ArgumentNullException(nameof(block));
|
|
if (!block.IsOpen) throw new ArgumentException("The block must be opened", nameof(block));
|
|
OpenedBlocks.Add(block);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force closing the specified block.
|
|
/// </summary>
|
|
/// <param name="block">The block.</param>
|
|
public void Close(Block block)
|
|
{
|
|
// If we close a block, we close all blocks above
|
|
for (int i = OpenedBlocks.Count - 1; i >= 0; i--)
|
|
{
|
|
if (OpenedBlocks[i] == block)
|
|
{
|
|
for (int j = OpenedBlocks.Count - 1; j >= i; j--)
|
|
{
|
|
Close(j);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Discards the specified block from the stack, remove from its parent.
|
|
/// </summary>
|
|
/// <param name="block">The block.</param>
|
|
public void Discard(Block block)
|
|
{
|
|
for (int i = OpenedBlocks.Count - 1; i >= 1; i--)
|
|
{
|
|
if (OpenedBlocks[i] == block)
|
|
{
|
|
block.Parent.Remove(block);
|
|
OpenedBlocks.RemoveAt(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes a new line.
|
|
/// </summary>
|
|
/// <param name="newLine">The new line.</param>
|
|
public void ProcessLine(StringSlice newLine)
|
|
{
|
|
ContinueProcessingLine = true;
|
|
|
|
ResetLine(newLine);
|
|
|
|
TryContinueBlocks();
|
|
|
|
// If the line was not entirely processed by pending blocks, try to process it with any new block
|
|
TryOpenBlocks();
|
|
|
|
// Close blocks that are no longer opened
|
|
CloseAll(false);
|
|
|
|
LineIndex++;
|
|
}
|
|
|
|
public BlockProcessor CreateChild()
|
|
{
|
|
var newState = parserStateCache.Get();
|
|
return newState;
|
|
}
|
|
|
|
public void ReleaseChild()
|
|
{
|
|
if (this == root)
|
|
{
|
|
throw new InvalidOperationException("Cannot release the root parser state");
|
|
}
|
|
parserStateCache.Release(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes a block at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The index.</param>
|
|
private void Close(int index)
|
|
{
|
|
var block = OpenedBlocks[index];
|
|
// If the pending object is removed, we need to remove it from the parent container
|
|
if (block.Parser != null && !block.Parser.Close(this, block))
|
|
{
|
|
block.Parent?.Remove(block);
|
|
}
|
|
OpenedBlocks.RemoveAt(index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes all the blocks opened.
|
|
/// </summary>
|
|
/// <param name="force">if set to <c>true</c> [force].</param>
|
|
internal void CloseAll(bool force)
|
|
{
|
|
// Close any previous blocks not opened
|
|
for (int i = OpenedBlocks.Count - 1; i >= 1; i--)
|
|
{
|
|
var block = OpenedBlocks[i];
|
|
|
|
// Stop on the first open block
|
|
if (!force && block.IsOpen)
|
|
{
|
|
break;
|
|
}
|
|
Close(i);
|
|
}
|
|
UpdateLastBlockAndContainer();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mark all blocks in the stack as opened.
|
|
/// </summary>
|
|
private void OpenAll()
|
|
{
|
|
for (int i = 1; i < OpenedBlocks.Count; i++)
|
|
{
|
|
OpenedBlocks[i].IsOpen = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the <see cref="CurrentBlock"/> and <see cref="CurrentContainer"/>.
|
|
/// </summary>
|
|
/// <param name="stackIndex">Index of a block in a stack considered as the last block to update from.</param>
|
|
private void UpdateLastBlockAndContainer(int stackIndex = -1)
|
|
{
|
|
currentStackIndex = stackIndex < 0 ? OpenedBlocks.Count - 1 : stackIndex;
|
|
CurrentBlock = null;
|
|
LastBlock = null;
|
|
for (int i = OpenedBlocks.Count - 1; i >= 0; i--)
|
|
{
|
|
var block = OpenedBlocks[i];
|
|
if (CurrentBlock == null)
|
|
{
|
|
CurrentBlock = block;
|
|
}
|
|
|
|
var container = block as ContainerBlock;
|
|
if (container != null)
|
|
{
|
|
CurrentContainer = container;
|
|
LastBlock = CurrentContainer.LastChild;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to continue matching existing opened <see cref="Block"/>.
|
|
/// </summary>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// A pending parser cannot add a new block when it is not the last pending block
|
|
/// or
|
|
/// The NewBlocks is not empty. This is happening if a LeafBlock is not the last to be pushed
|
|
/// </exception>
|
|
private void TryContinueBlocks()
|
|
{
|
|
// Set all blocks non opened.
|
|
// They will be marked as open in the following loop
|
|
for (int i = 1; i < OpenedBlocks.Count; i++)
|
|
{
|
|
OpenedBlocks[i].IsOpen = false;
|
|
}
|
|
|
|
// Process any current block potentially opened
|
|
for (int i = 1; i < OpenedBlocks.Count; i++)
|
|
{
|
|
var block = OpenedBlocks[i];
|
|
|
|
ParseIndent();
|
|
|
|
// If we have a paragraph block, we want to try to match other blocks before trying the Paragraph
|
|
if (block is ParagraphBlock)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Else tries to match the Default with the current line
|
|
var parser = block.Parser;
|
|
|
|
|
|
// If we have a discard, we can remove it from the current state
|
|
UpdateLastBlockAndContainer(i);
|
|
var result = parser.TryContinue(this, block);
|
|
if (result == BlockState.Skip)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (result == BlockState.None)
|
|
{
|
|
break;
|
|
}
|
|
|
|
RestartIndent();
|
|
|
|
// In case the BlockParser has modified the BlockProcessor we are iterating on
|
|
if (i >= OpenedBlocks.Count)
|
|
{
|
|
i = OpenedBlocks.Count - 1;
|
|
}
|
|
|
|
// If a parser is adding a block, it must be the last of the list
|
|
if ((i + 1) < OpenedBlocks.Count && NewBlocks.Count > 0)
|
|
{
|
|
throw new InvalidOperationException("A pending parser cannot add a new block when it is not the last pending block");
|
|
}
|
|
|
|
// If we have a leaf block
|
|
var leaf = block as LeafBlock;
|
|
if (leaf != null && NewBlocks.Count == 0)
|
|
{
|
|
ContinueProcessingLine = false;
|
|
if (!result.IsDiscard())
|
|
{
|
|
leaf.AppendLine(ref Line, Column, LineIndex);
|
|
}
|
|
|
|
if (NewBlocks.Count > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The NewBlocks is not empty. This is happening if a LeafBlock is not the last to be pushed");
|
|
}
|
|
}
|
|
|
|
// A block is open only if it has a Continue state.
|
|
// otherwise it is a Break state, and we don't keep it opened
|
|
block.IsOpen = result == BlockState.Continue || result == BlockState.ContinueDiscard;
|
|
|
|
if (result == BlockState.BreakDiscard)
|
|
{
|
|
ContinueProcessingLine = false;
|
|
break;
|
|
}
|
|
|
|
bool isLast = i == OpenedBlocks.Count - 1;
|
|
if (ContinueProcessingLine)
|
|
{
|
|
ProcessNewBlocks(result, false);
|
|
}
|
|
if (isLast || !ContinueProcessingLine)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// First phase of the process, try to open new blocks.
|
|
/// </summary>
|
|
private void TryOpenBlocks()
|
|
{
|
|
while (ContinueProcessingLine)
|
|
{
|
|
// Eat indent spaces before checking the character
|
|
ParseIndent();
|
|
|
|
var parsers = Parsers.GetParsersForOpeningCharacter(CurrentChar);
|
|
var globalParsers = Parsers.GlobalParsers;
|
|
|
|
if (parsers != null)
|
|
{
|
|
if (TryOpenBlocks(parsers))
|
|
{
|
|
RestartIndent();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (globalParsers != null && ContinueProcessingLine)
|
|
{
|
|
if (TryOpenBlocks(globalParsers))
|
|
{
|
|
RestartIndent();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to open new blocks using the specified list of <see cref="BlockParser"/>
|
|
/// </summary>
|
|
/// <param name="parsers">The parsers.</param>
|
|
/// <returns><c>true</c> to continue processing the current line</returns>
|
|
private bool TryOpenBlocks(BlockParser[] parsers)
|
|
{
|
|
for (int j = 0; j < parsers.Length; j++)
|
|
{
|
|
var blockParser = parsers[j];
|
|
if (Line.IsEmpty)
|
|
{
|
|
ContinueProcessingLine = false;
|
|
break;
|
|
}
|
|
|
|
// UpdateLastBlockAndContainer the state of CurrentBlock and LastContainer
|
|
UpdateLastBlockAndContainer();
|
|
|
|
// If a block parser cannot interrupt a paragraph, and the last block is a paragraph
|
|
// we can skip this parser
|
|
|
|
var lastBlock = CurrentBlock;
|
|
if (!blockParser.CanInterrupt(this, lastBlock))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool isLazyParagraph = blockParser is ParagraphBlockParser && lastBlock is ParagraphBlock;
|
|
|
|
var result = isLazyParagraph
|
|
? blockParser.TryContinue(this, lastBlock)
|
|
: blockParser.TryOpen(this);
|
|
|
|
if (result == BlockState.None)
|
|
{
|
|
// If we have reached a blank line after trying to parse a paragraph
|
|
// we can ignore it
|
|
if (isLazyParagraph && IsBlankLine)
|
|
{
|
|
ContinueProcessingLine = false;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Special case for paragraph
|
|
UpdateLastBlockAndContainer();
|
|
|
|
var paragraph = CurrentBlock as ParagraphBlock;
|
|
if (isLazyParagraph && paragraph != null)
|
|
{
|
|
Debug.Assert(NewBlocks.Count == 0);
|
|
|
|
if (!result.IsDiscard())
|
|
{
|
|
paragraph.AppendLine(ref Line, Column, LineIndex);
|
|
}
|
|
|
|
// We have just found a lazy continuation for a paragraph, early exit
|
|
// Mark all block opened after a lazy continuation
|
|
OpenAll();
|
|
|
|
ContinueProcessingLine = false;
|
|
break;
|
|
}
|
|
|
|
// Nothing found but the BlockParser may instruct to break, so early exit
|
|
if (NewBlocks.Count == 0 && result == BlockState.BreakDiscard)
|
|
{
|
|
ContinueProcessingLine = false;
|
|
break;
|
|
}
|
|
|
|
// If we have a container, we can retry to match against all types of block.
|
|
ProcessNewBlocks(result, true);
|
|
return ContinueProcessingLine;
|
|
|
|
// We have a leaf node, we can stop
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes any new blocks that have been pushed to <see cref="NewBlocks"/>.
|
|
/// </summary>
|
|
/// <param name="result">The last result of matching.</param>
|
|
/// <param name="allowClosing">if set to <c>true</c> the processing of a new block will close existing opened blocks].</param>
|
|
/// <exception cref="System.InvalidOperationException">The NewBlocks is not empty. This is happening if a LeafBlock is not the last to be pushed</exception>
|
|
private void ProcessNewBlocks(BlockState result, bool allowClosing)
|
|
{
|
|
var newBlocks = NewBlocks;
|
|
while (newBlocks.Count > 0)
|
|
{
|
|
var block = newBlocks.Pop();
|
|
|
|
if (block.Parser == null)
|
|
{
|
|
throw new InvalidOperationException($"The new block [{block.GetType()}] must have a valid Parser property");
|
|
}
|
|
|
|
block.Line = LineIndex;
|
|
|
|
// If we have a leaf block
|
|
var leaf = block as LeafBlock;
|
|
if (leaf != null)
|
|
{
|
|
if (!result.IsDiscard())
|
|
{
|
|
leaf.AppendLine(ref Line, Column, LineIndex);
|
|
}
|
|
|
|
if (newBlocks.Count > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The NewBlocks is not empty. This is happening if a LeafBlock is not the last to be pushed");
|
|
}
|
|
}
|
|
|
|
if (allowClosing)
|
|
{
|
|
// Close any previous blocks not opened
|
|
CloseAll(false);
|
|
}
|
|
|
|
// If previous block is a container, add the new block as a children of the previous block
|
|
if (block.Parent == null)
|
|
{
|
|
UpdateLastBlockAndContainer();
|
|
CurrentContainer.Add(block);
|
|
}
|
|
|
|
block.IsOpen = result.IsContinue();
|
|
|
|
// Add a block BlockProcessor to the stack (and leave it opened)
|
|
OpenedBlocks.Add(block);
|
|
|
|
if (leaf != null)
|
|
{
|
|
ContinueProcessingLine = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ContinueProcessingLine = !result.IsDiscard();
|
|
}
|
|
|
|
private void ResetLine(StringSlice newLine)
|
|
{
|
|
Line = newLine;
|
|
Column = 0;
|
|
ColumnBeforeIndent = 0;
|
|
StartBeforeIndent = Start;
|
|
originalLineStart = newLine.Start;
|
|
}
|
|
|
|
private void Reset()
|
|
{
|
|
Line = StringSlice.Empty;
|
|
Column = 0;
|
|
ColumnBeforeIndent = 0;
|
|
StartBeforeIndent = 0;
|
|
OpenedBlocks.Clear();
|
|
NewBlocks.Clear();
|
|
}
|
|
|
|
private class BlockParserStateCache : ObjectCache<BlockProcessor>
|
|
{
|
|
private readonly BlockProcessor root;
|
|
|
|
public BlockParserStateCache(BlockProcessor root)
|
|
{
|
|
this.root = root;
|
|
}
|
|
|
|
protected override BlockProcessor NewInstance()
|
|
{
|
|
return new BlockProcessor(root);
|
|
}
|
|
|
|
protected override void Reset(BlockProcessor instance)
|
|
{
|
|
instance.Reset();
|
|
}
|
|
}
|
|
}
|
|
} |