Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimized away the expensive StringBuilder.Remove(0, 1) in CodeInlineParser #418

Merged
merged 2 commits into from
Apr 18, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 103 additions & 98 deletions src/Markdig/Parsers/Inlines/CodeInlineParser.cs
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
// 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 Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;

namespace Markdig.Parsers.Inlines
{
/// <summary>
/// An inline parser for a <see cref="CodeInline"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.InlineParser" />
public class CodeInlineParser : InlineParser
{
/// <summary>
/// Initializes a new instance of the <see cref="CodeInlineParser"/> class.
/// </summary>
public CodeInlineParser()
{
OpeningCharacters = new[] { '`' };
}

public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
var match = slice.CurrentChar;
if (slice.PeekCharExtra(-1) == match)
{
return false;
}

var startPosition = slice.Start;

int openSticks = 0;
int closeSticks = 0;

// Match the opened sticks
char c = slice.CurrentChar;
while (c == match)
{
openSticks++;
c = slice.NextChar();
}

// 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 Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Parsers.Inlines
{
/// <summary>
/// An inline parser for a <see cref="CodeInline"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.InlineParser" />
public class CodeInlineParser : InlineParser
{
/// <summary>
/// Initializes a new instance of the <see cref="CodeInlineParser"/> class.
/// </summary>
public CodeInlineParser()
{
OpeningCharacters = new[] { '`' };
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
var match = slice.CurrentChar;
if (slice.PeekCharExtra(-1) == match)
{
return false;
}
var startPosition = slice.Start;
int openSticks = 0;
int closeSticks = 0;
// Match the opened sticks
char c = slice.CurrentChar;
while (c == match)
{
openSticks++;
c = slice.NextChar();
}
var builder = processor.StringBuilders.Get();

// A backtick string is a string of one or more backtick characters (`) that is neither preceded nor followed by a backtick.
Expand All @@ -53,71 +53,76 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
// 2. If the resulting string both begins AND ends with a space character, but does not consist entirely
// of space characters, a single space character is removed from the front and back.
// This allows you to include code that begins or ends with backtick characters, which must be separated by
// whitespace from the opening or closing backtick strings.

bool allSpace = true;

while (c != '\0')
{
// Transform '\n' into a single space
if (c == '\n')
{
c = ' ';
}

// whitespace from the opening or closing backtick strings.
bool allSpace = true;
while (c != '\0')
{
// Transform '\n' into a single space
if (c == '\n')
{
c = ' ';
}
if (c == match)
{
do
{
closeSticks++;
c = slice.NextChar();
do
{
closeSticks++;
c = slice.NextChar();
}
while (c == match);

if (openSticks == closeSticks)
{
break;
}

allSpace = false;
builder.Append(match, closeSticks);
if (openSticks == closeSticks)
{
break;
}
allSpace = false;
builder.Append(match, closeSticks);
closeSticks = 0;
}
}
else
{
builder.Append(c);
if (c != ' ')
{
allSpace = false;
}
}
c = slice.NextChar();
}
}

bool isMatching = false;
if (closeSticks == openSticks)
{
// Remove one space from front and back if the string is not all spaces
if (!allSpace && builder.Length > 2 && builder[0] == ' ' && builder[builder.Length - 1] == ' ')
{
builder.Length--;
builder.Remove(0, 1); // More expensive, alternative is to have a double-pass algorithm
}

processor.Inline = new CodeInline()
{
Delimiter = match,
Content = builder.ToString(),
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)),
Line = line,
Column = column
};
isMatching = true;
}

// Release the builder if not used
processor.StringBuilders.Release(builder);
return isMatching;
}
}
}
}

bool isMatching = false;
if (closeSticks == openSticks)
{
string content;

// Remove one space from front and back if the string is not all spaces
if (!allSpace && builder.Length > 2 && builder[0] == ' ' && builder[builder.Length - 1] == ' ')
{
content = builder.ToString(1, builder.Length - 2);
}
else
{
content = builder.ToString();
}

processor.Inline = new CodeInline()
{
Delimiter = match,
Content = content,
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)),
Line = line,
Column = column
};
isMatching = true;
}

// Release the builder if not used
processor.StringBuilders.Release(builder);
return isMatching;
}
}
}