Temp work

This commit is contained in:
Alexandre Mutel
2016-02-25 20:30:37 +09:00
parent 4e9aad9fa9
commit 0976c03dcf
34 changed files with 751 additions and 1030 deletions

View File

@@ -40,7 +40,7 @@ namespace Testamina.Markdig.Benchmarks
//CommonMark.CommonMarkConverter.Convert(new StringReader(text), new StringWriter());
//var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
var reader = new StringReader(text);
CommonMark.CommonMarkConverter.Parse(reader);
CommonMark.CommonMarkConverter.ProcessStage1(reader);
reader.Dispose();
}
@@ -50,8 +50,8 @@ namespace Testamina.Markdig.Benchmarks
var program = new Program();
for (int i = 0; i < 200; i++)
{
program.TestMarkdig();
//program.TestCommonMark();
//program.TestMarkdig();
program.TestCommonMark();
}
Console.WriteLine($"time: {clock.ElapsedMilliseconds}ms");
DumpGC();

View File

@@ -14,7 +14,7 @@ namespace Textamina.Markdig.Tests
public void TestParseHtmlTagSimple()
{
var inputTag = "<a>";
var text = new StringLineGroup(inputTag);
var text = new StringSlice(inputTag);
string outputTag;
Assert.True(HtmlHelper.TryParseHtmlTag(text, out outputTag));
Assert.AreEqual(inputTag, outputTag);
@@ -24,7 +24,7 @@ namespace Textamina.Markdig.Tests
public void TestParseHtmlTagSimpleWithAttribute()
{
var inputTag = "<a href='http://google.com'>";
var text = new StringLineGroup(inputTag);
var text = new StringSlice(inputTag);
string outputTag;
Assert.True(HtmlHelper.TryParseHtmlTag(text, out outputTag));
Assert.AreEqual(inputTag, outputTag);

View File

@@ -10,9 +10,9 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlSimple()
{
var text = new StringLineGroup("toto tutu");
var text = new StringSlice("toto tutu");
string link;
Assert.True(LinkHelper.TryParseUrl(text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.AreEqual("toto", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -20,9 +20,9 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlUrl()
{
var text = new StringLineGroup("http://google.com)");
var text = new StringSlice("http://google.com)");
string link;
Assert.True(LinkHelper.TryParseUrl(text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual(')', text.CurrentChar);
}
@@ -30,9 +30,9 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlNestedParenthesis()
{
var text = new StringLineGroup("(toto)tutu(tata) nooo");
var text = new StringSlice("(toto)tutu(tata) nooo");
string link;
Assert.True(LinkHelper.TryParseUrl(text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.AreEqual("(toto)tutu(tata)", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -40,9 +40,9 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlAlternate()
{
var text = new StringLineGroup("<toto_tata_tutu> nooo");
var text = new StringSlice("<toto_tata_tutu> nooo");
string link;
Assert.True(LinkHelper.TryParseUrl(text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.AreEqual("toto_tata_tutu", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -50,26 +50,26 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlAlternateInvalid()
{
var text = new StringLineGroup("<toto_tata_tutu");
var text = new StringSlice("<toto_tata_tutu");
string link;
Assert.False(LinkHelper.TryParseUrl(text, out link));
Assert.False(LinkHelper.TryParseUrl(ref text, out link));
}
[Test]
public void TestTitleSimple()
{
var text = new StringLineGroup(@"'tata\tutu\''");
var text = new StringSlice(@"'tata\tutu\''");
string title;
Assert.True(LinkHelper.TryParseTitle(text, out title));
Assert.True(LinkHelper.TryParseTitle(ref text, out title));
Assert.AreEqual(@"tata\tutu'", title);
}
[Test]
public void TestTitleSimpleAlternate()
{
var text = new StringLineGroup(@"""tata\tutu\"""" ");
var text = new StringSlice(@"""tata\tutu\"""" ");
string title;
Assert.True(LinkHelper.TryParseTitle(text, out title));
Assert.True(LinkHelper.TryParseTitle(ref text, out title));
Assert.AreEqual(@"tata\tutu""", title);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -77,10 +77,10 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlAndTitle()
{
var text = new StringLineGroup(@"(http://google.com 'this is a title')ABC");
var text = new StringSlice(@"(http://google.com 'this is a title')ABC");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("this is a title", title);
Assert.AreEqual('A', text.CurrentChar);
@@ -89,10 +89,10 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlAndTitleEmpty()
{
var text = new StringLineGroup(@"(<>)A");
var text = new StringSlice(@"(<>)A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual('A', text.CurrentChar);
@@ -101,10 +101,10 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlAndTitleEmpty2()
{
var text = new StringLineGroup(@"( <> )A");
var text = new StringSlice(@"( <> )A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual('A', text.CurrentChar);
@@ -114,10 +114,10 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlEmptyWithTitleWithMultipleSpaces()
{
var text = new StringLineGroup(@"( <> 'toto' )A");
var text = new StringSlice(@"( <> 'toto' )A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual("toto", title);
Assert.AreEqual('A', text.CurrentChar);
@@ -126,10 +126,10 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestUrlEmpty()
{
var text = new StringLineGroup(@"()A");
var text = new StringSlice(@"()A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual('A', text.CurrentChar);
@@ -138,45 +138,41 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestMultipleLines()
{
var text = new StringLineGroup()
{
new StringLine("("),
new StringLine(" <http://google.com> "),
new StringLine(" 'toto' )A")
};
var text = new StringSlice(@"(
<http://google.com>
'toto' )A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("toto", title);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestLabelSimple()
{
var text = new StringLineGroup("[foo]");
var text = new StringSlice("[foo]");
string label;
Assert.True(LinkHelper.TryParseLabel(text, out label));
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
Assert.AreEqual("foo", label);
}
[Test]
public void TestLabelEscape()
{
var text = new StringLineGroup(@"[fo\[\]o]");
var text = new StringSlice(@"[fo\[\]o]");
string label;
Assert.True(LinkHelper.TryParseLabel(text, out label));
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
Assert.AreEqual(@"fo[]o", label);
}
[Test]
public void TestLabelEscape2()
{
var text = new StringLineGroup(@"[\]]");
var text = new StringSlice(@"[\]]");
string label;
Assert.True(LinkHelper.TryParseLabel(text, out label));
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
Assert.AreEqual(@"]", label);
}
@@ -184,31 +180,31 @@ namespace Textamina.Markdig.Tests
public void TestLabelInvalids()
{
string label;
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"a"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"["), out label));
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"[\x]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"[[]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"[ ]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"[ \t \n ]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"a"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"["), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[\x]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[[]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[ ]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[ \t \n ]"), out label));
}
[Test]
public void TestLabelWhitespaceCollapsedAndTrim()
{
var text = new StringLineGroup(@"[ fo o z ]");
var text = new StringSlice(@"[ fo o z ]");
string label;
Assert.True(LinkHelper.TryParseLabel(text, out label));
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
Assert.AreEqual(@"fo o z", label);
}
[Test]
public void TestlLinkReferenceDefinitionSimple()
{
var text = new StringLineGroup(@"[foo]: /toto 'title'");
var text = new StringSlice(@"[foo]: /toto 'title'");
string label;
string url;
string title;
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(text, out label, out url, out title));
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title));
Assert.AreEqual(@"foo", label);
Assert.AreEqual(@"/toto", url);
Assert.AreEqual(@"title", title);
@@ -217,10 +213,10 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestAutoLinkUrlSimple()
{
var text = new StringLineGroup(@"<http://google.com>");
var text = new StringSlice(@"<http://google.com>");
string url;
bool isEmail;
Assert.True(LinkHelper.TryParseAutolink(text, out url, out isEmail));
Assert.True(LinkHelper.TryParseAutolink(ref text, out url, out isEmail));
Assert.False(isEmail);
Assert.AreEqual("http://google.com", url);
}
@@ -228,10 +224,10 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestAutoLinkEmailSimple()
{
var text = new StringLineGroup(@"<user@host.com>");
var text = new StringSlice(@"<user@host.com>");
string email;
bool isEmail;
Assert.True(LinkHelper.TryParseAutolink(text, out email, out isEmail));
Assert.True(LinkHelper.TryParseAutolink(ref text, out email, out isEmail));
Assert.True(isEmail);
Assert.AreEqual("user@host.com", email);
}
@@ -241,10 +237,10 @@ namespace Textamina.Markdig.Tests
{
string text;
bool isEmail;
Assert.False(LinkHelper.TryParseAutolink(new StringLineGroup(@""), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringLineGroup(@"<"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringLineGroup(@"<ab"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringLineGroup(@"<user@>"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@""), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<ab"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<user@>"), out text, out isEmail));
}
}
}

View File

@@ -24,7 +24,7 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestSimple()
{
var reader = new StringReader(@"####### foo");
var reader = new StringReader(@"foo");
// var reader = new StringReader(@"> > toto tata
//> titi toto
//");

View File

@@ -8,24 +8,25 @@ namespace Textamina.Markdig.Tests
[TestFixture]
public class TestStringLine
{
// TODO: Add tests for StringLine
/*
// TODO: Add tests for StringSlice
// TODO: Add more tests for StringLineGroup
[Test]
public void TestStringLineGroupSimple()
{
var text = new StringLineGroup()
var text = new StringSliceList()
{
new StringLine("ABC"),
new StringLine("E"),
new StringLine("F")
new StringSlice("ABC"),
new StringSlice("E"),
new StringSlice("F")
};
var chars = ToString(text);
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
}
private static string ToString(StringLineGroup text)
private static string ToString(StringSliceList text)
{
var chars = new StringBuilder();
while (text.CurrentChar != '\0')
@@ -39,10 +40,10 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestStringLineGroupSaveAndRestore()
{
var text = new StringLineGroup()
var text = new StringSliceList()
{
new StringLine("ABCD"),
new StringLine("EF"),
new StringSlice("ABCD"),
new StringSlice("EF"),
};
text.NextChar(); // B
@@ -66,7 +67,7 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestSkipWhitespaces()
{
var text = new StringLineGroup(" ABC");
var text = new StringSliceList(" ABC");
Assert.True(text.SkipWhiteSpaces());
Assert.False(text.SkipWhiteSpaces());
Assert.AreEqual('A', text.CurrentChar);
@@ -75,14 +76,14 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestStringLineGroupWithModifiedStart()
{
var line1 = new StringLine(" ABC");
var line1 = new StringSlice(" ABC");
line1.NextChar();
line1.NextChar();
var line2 = new StringLine(" DEF ");
var line2 = new StringSlice(" DEF ");
line2.Trim();
var text = new StringLineGroup() {line1, line2};
var text = new StringSliceList() {line1, line2};
var result = ToString(text);
TextAssert.AreEqual("ABC\nDEF", result);
@@ -92,19 +93,19 @@ namespace Textamina.Markdig.Tests
[Test]
public void TestStringLineGroupWithTrim()
{
var line1 = new StringLine(" ABC ");
var line1 = new StringSlice(" ABC ");
line1.NextChar();
line1.NextChar();
var line2 = new StringLine(" DEF ");
var line2 = new StringSlice(" DEF ");
var text = new StringLineGroup() { line1, line2 };
var text = new StringSliceList() { line1, line2 };
text.Trim();
var result = ToString(text);
TextAssert.AreEqual("ABC \n DEF", result);
}
*/
}
}

View File

@@ -26,7 +26,7 @@ namespace Textamina.Markdig.Formatters
[typeof(FencedCodeBlock)] = o => Write((FencedCodeBlock)o),
[typeof(CodeBlock)] = o => Write((CodeBlock)o),
[typeof(HeadingBlock)] = o => Write((HeadingBlock)o),
[typeof(BreakBlock)] = o => Write((BreakBlock)o),
[typeof(ThematicBreakBlock)] = o => Write((ThematicBreakBlock)o),
[typeof(QuoteBlock)] = o => Write((QuoteBlock)o),
[typeof(ParagraphBlock)] = o => Write((ParagraphBlock)o),
[typeof(HtmlBlock)] = o => Write((HtmlBlock)o),
@@ -135,7 +135,7 @@ namespace Textamina.Markdig.Formatters
writer.WriteLineConstant(">");
}
protected void Write(BreakBlock breakBlock)
protected void Write(ThematicBreakBlock breakBlock)
{
writer.WriteLineConstant("<hr />");
}
@@ -282,7 +282,7 @@ namespace Textamina.Markdig.Formatters
{
writer.WriteLine();
}
var line = lines[i];
var line = lines.Slices[i];
if (escape)
{
HtmlHelper.EscapeHtml(line.ToString(), writer);

View File

@@ -55,16 +55,22 @@ namespace Textamina.Markdig.Helpers
"XMLRPC.BEEP", "XMLRPC.BEEPS", "XMPP", "XRI", "YMSGR", "Z39.50R", "Z39.50S"
};
public static bool TryParseHtmlTag(StringLineGroup text, out string htmlTag)
public static bool TryParseHtmlTag(StringSlice text, out string htmlTag)
{
return TryParseHtmlTag(ref text, out htmlTag);
}
public static bool TryParseHtmlTag(ref StringSlice text, out string htmlTag)
{
var builder = StringBuilderCache.Local();
var result = TryParseHtmlTag(text, builder);
var result = TryParseHtmlTag(ref text, builder);
htmlTag = builder.ToString();
builder.Clear();
return result;
}
public static bool TryParseHtmlTag(StringLineGroup text, StringBuilder builder)
public static bool TryParseHtmlTag(ref StringSlice text, StringBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
var c = text.CurrentChar;
@@ -101,7 +107,7 @@ namespace Textamina.Markdig.Helpers
return TryParseHtmlTagOpenTag(text, builder);
}
internal static bool TryParseHtmlTagOpenTag(StringLineGroup text, StringBuilder builder)
internal static bool TryParseHtmlTagOpenTag(StringSlice text, StringBuilder builder)
{
var c = text.CurrentChar;
@@ -256,7 +262,7 @@ namespace Textamina.Markdig.Helpers
}
}
private static bool TryParseHtmlTagDeclaration(StringLineGroup text, StringBuilder builder)
private static bool TryParseHtmlTagDeclaration(StringSlice text, StringBuilder builder)
{
var c = text.CurrentChar;
bool hasAlpha = false;
@@ -291,7 +297,7 @@ namespace Textamina.Markdig.Helpers
}
}
private static bool TryParseHtmlTagCData(StringLineGroup text, StringBuilder builder)
private static bool TryParseHtmlTagCData(StringSlice text, StringBuilder builder)
{
builder.Append('[');
var c = text.NextChar();
@@ -334,7 +340,7 @@ namespace Textamina.Markdig.Helpers
return false;
}
internal static bool TryParseHtmlCloseTag(StringLineGroup text, StringBuilder builder)
internal static bool TryParseHtmlCloseTag(StringSlice text, StringBuilder builder)
{
// </[A-Za-z][A-Za-z0-9]+\s*>
builder.Append('/');
@@ -379,7 +385,7 @@ namespace Textamina.Markdig.Helpers
}
private static bool TryParseHtmlTagHtmlComment(StringLineGroup text, StringBuilder builder)
private static bool TryParseHtmlTagHtmlComment(StringSlice text, StringBuilder builder)
{
var c = text.NextChar();
if (c != '-')
@@ -388,7 +394,7 @@ namespace Textamina.Markdig.Helpers
}
builder.Append('-');
builder.Append('-');
if (text.PeekCharOnSameLine() == '>')
if (text.PeekChar(1) == '>')
{
return false;
}
@@ -417,7 +423,7 @@ namespace Textamina.Markdig.Helpers
}
}
private static bool TryParseHtmlTagProcessingInstruction(StringLineGroup text, StringBuilder builder)
private static bool TryParseHtmlTagProcessingInstruction(StringSlice text, StringBuilder builder)
{
builder.Append('?');
var prevChar = '\0';

View File

@@ -8,7 +8,12 @@ namespace Textamina.Markdig.Helpers
{
public static class LinkHelper
{
public static bool TryParseAutolink(StringLineGroup text, out string link, out bool isEmail)
public static bool TryParseAutolink(StringSlice text, out string link, out bool isEmail)
{
return TryParseAutolink(ref text, out link, out isEmail);
}
public static bool TryParseAutolink(ref StringSlice text, out string link, out bool isEmail)
{
link = null;
isEmail = false;
@@ -205,10 +210,13 @@ namespace Textamina.Markdig.Helpers
return false;
}
public static bool TryParseInlineLink(StringLineGroup text, out string link, out string title)
public static bool TryParseInlineLink(StringSlice text, out string link, out string title)
{
if (text == null) throw new ArgumentNullException(nameof(text));
return TryParseInlineLink(ref text, out link, out title);
}
public static bool TryParseInlineLink(ref StringSlice text, out string link, out string title)
{
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
// 2. optional whitespace, TODO: specs: is it whitespace or multiple whitespaces?
// 3. an optional link destination,
@@ -224,11 +232,11 @@ namespace Textamina.Markdig.Helpers
if (c == '(')
{
text.NextChar();
text.SkipWhiteSpaces();
text.TrimStart();
if (TryParseUrl(text, out link))
if (TryParseUrl(ref text, out link))
{
var hasWhiteSpaces = text.SkipWhiteSpaces();
var hasWhiteSpaces = text.TrimStart();
c = text.CurrentChar;
if (c == ')')
@@ -242,9 +250,9 @@ namespace Textamina.Markdig.Helpers
{
isValid = true;
}
else if (TryParseTitle(text, out title))
else if (TryParseTitle(ref text, out title))
{
text.SkipWhiteSpaces();
text.TrimStart();
c = text.CurrentChar;
if (c == ')')
@@ -266,10 +274,13 @@ namespace Textamina.Markdig.Helpers
return isValid;
}
public static bool TryParseTitle(StringLineGroup text, out string title)
public static bool TryParseTitle(StringSlice text, out string title)
{
if (text == null) throw new ArgumentNullException(nameof(text));
return TryParseTitle(ref text, out title);
}
public static bool TryParseTitle(ref StringSlice text, out string title)
{
bool isValid = false;
var buffer = StringBuilderCache.Local();
buffer.Clear();
@@ -286,7 +297,7 @@ namespace Textamina.Markdig.Helpers
{
c = text.NextChar();
if (text.Current != null && text.Current.IsBlankLine())
if (c == '\n') // TODO?
{
break;
}
@@ -333,10 +344,13 @@ namespace Textamina.Markdig.Helpers
return isValid;
}
public static bool TryParseUrl(StringLineGroup text, out string link)
public static bool TryParseUrl(StringSlice text, out string link)
{
if (text == null) throw new ArgumentNullException(nameof(text));
return TryParseUrl(ref text, out link);
}
public static bool TryParseUrl(ref StringSlice text, out string link)
{
bool isValid = false;
var buffer = StringBuilderCache.Local();
buffer.Clear();
@@ -453,12 +467,18 @@ namespace Textamina.Markdig.Helpers
return isValid;
}
public static bool TryParseLinkReferenceDefinition(StringLineGroup text, out string label, out string url,
public static bool TryParseLinkReferenceDefinition(StringSlice text, out string label, out string url,
out string title)
{
return TryParseLinkReferenceDefinition(ref text, out label, out url, out title);
}
public static bool TryParseLinkReferenceDefinition(ref StringSlice text, out string label, out string url,
out string title)
{
url = null;
title = null;
if (!TryParseLabel(text, out label))
if (!TryParseLabel(ref text, out label))
{
return false;
}
@@ -471,19 +491,19 @@ namespace Textamina.Markdig.Helpers
text.NextChar(); // Skip ':'
// Skip any whitespaces before the url
text.SkipWhiteSpaces();
if (!TryParseUrl(text, out url) || string.IsNullOrEmpty(url))
text.TrimStart();
if (!TryParseUrl(ref text, out url) || string.IsNullOrEmpty(url))
{
return false;
}
var saved = text.Save();
var saved = text;
int newLineCount;
var hasWhiteSpaces = text.SkipWhiteSpaces(out newLineCount);
var hasWhiteSpaces = text.TrimStart(out newLineCount);
var c = text.CurrentChar;
if (c == '\'' || c == '"' || c == '(')
{
if (TryParseTitle(text, out title))
if (TryParseTitle(ref text, out title))
{
// If we have a title, it requires a whitespace after the url
if (!hasWhiteSpaces)
@@ -517,7 +537,7 @@ namespace Textamina.Markdig.Helpers
// we are still returning a valid definition
if (newLineCount > 0 && title != null)
{
text.Restore(ref saved);
text = saved;
title = null;
return true;
}
@@ -531,13 +551,17 @@ namespace Textamina.Markdig.Helpers
return true;
}
public static bool TryParseLabel(StringLineGroup lines, out string label)
public static bool TryParseLabel(StringSlice lines, out string label)
{
return TryParseLabel(lines, false, out label);
return TryParseLabel(ref lines, false, out label);
}
public static bool TryParseLabel(ref StringSlice lines, out string label)
{
return TryParseLabel(ref lines, false, out label);
}
public static bool TryParseLabel(StringLineGroup lines, bool allowEmpty, out string label)
public static bool TryParseLabel(ref StringSlice lines, bool allowEmpty, out string label)
{
label = null;
char c = lines.CurrentChar;

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Textamina.Markdig.Helpers;
using Textamina.Markdig.Syntax;
@@ -20,7 +21,32 @@ namespace Textamina.Markdig.Parsing
Add(root);
}
public StringLine Line;
public StringSlice Line;
public int LineIndex;
public bool IsBlankLine => CurrentChar == '\0';
public bool IsEndOfLine => Line.IsEndOfSlice;
public char CurrentChar => Line.CurrentChar;
public char NextChar() => Line.NextChar();
public char CharAt(int index) => Line[index];
public int Start => Line.Start;
public int EndOffset => Line.End;
public int Indent => Column - ColumnBegin;
public bool IsCodeIndent => Indent >= 4;
public int ColumnBegin { get; private set; }
public int Column { get; set; }
public Block Pending { get; set; }
@@ -36,6 +62,40 @@ namespace Textamina.Markdig.Parsing
public StringBuilderCache StringBuilders { get; }
public char PeekChar(int offset)
{
return Line.PeekChar(offset);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public void SetCurrentLine(ref StringSlice line)
{
Line = line;
EatSpaces();
}
public void EatSpaces()
{
var c = CurrentChar;
ColumnBegin = Column;
while (c !='\0')
{
if (c == ' ')
{
Column++;
}
else if (c == '\t')
{
Column = ((Column + 3) >> 2) << 2;
}
else
{
break;
}
c = NextChar();
}
}
public Block NextPending
{
get { return PendingIndex + 1 < Count ? this[PendingIndex + 1] : null; }
@@ -112,11 +172,5 @@ namespace Textamina.Markdig.Parsing
Close(i);
}
}
internal void Reset(StringLine line)
{
Line = line;
Pending = null;
}
}
}

View File

@@ -16,7 +16,7 @@ namespace Textamina.Markdig.Parsing
public LeafBlock Block { get; internal set; }
public StringLineGroup Lines;
public StringSlice Text;
public Inline Inline { get; set; }

View File

@@ -2,9 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Textamina.Markdig.Helpers;
using Textamina.Markdig.Syntax;
@@ -13,9 +11,6 @@ namespace Textamina.Markdig.Parsing
public class MarkdownParser
{
public static TextWriter Log;
private StringLine line;
private readonly List<BlockParser> blockParsers;
private readonly List<InlineParser> inlineParsers;
private readonly List<InlineParser> regularInlineParsers;
@@ -36,7 +31,7 @@ namespace Textamina.Markdig.Parsing
blockParserState = new BlockParserState(stringBuilderCache, document);
blockParsers = new List<BlockParser>()
{
BreakBlock.Parser,
ThematicBreakBlock.Parser,
HeadingBlock.Parser,
QuoteBlock.Parser,
ListBlock.Parser,
@@ -112,13 +107,12 @@ namespace Textamina.Markdig.Parsing
public Document Parse()
{
ParseLines();
ProcessInlines(document);
//ProcessInlines(document);
return document;
}
private void ParseLines()
{
int lineIndex = 0;
while (true)
{
var lineText = Reader.ReadLine();
@@ -128,13 +122,15 @@ namespace Textamina.Markdig.Parsing
{
break;
}
line = new StringLine(lineText, lineIndex++);
var line = new StringSlice(lineText);
blockParserState.SetCurrentLine(ref line);
blockParserState.LineIndex++;
bool continueProcessLiner = ProcessPendingBlocks();
// If we have already reached eol and the last block was a paragraph
// we close it
if (line.IsEol)
if (blockParserState.Line.IsEndOfSlice)
{
int index = blockParserState.Count - 1;
if (blockParserState[index] is ParagraphBlock)
@@ -203,7 +199,7 @@ namespace Textamina.Markdig.Parsing
}
// Create the line state that will be used by all parser
blockParserState.Reset(line);
blockParserState.Pending = null;
// Process any current block potentially opened
for (int i = 1; i < blockParserState.Count; i++)
@@ -220,7 +216,7 @@ namespace Textamina.Markdig.Parsing
break;
}
var saveLiner = line.Save();
var saveLiner = blockParserState.Line;
// If we have a discard, we can remove it from the current state
blockParserState.CurrentContainer = LastContainer;
@@ -235,7 +231,7 @@ namespace Textamina.Markdig.Parsing
if (result == MatchLineResult.None)
{
// Restore the Line where it was
line.Restore(ref saveLiner);
blockParserState.SetCurrentLine(ref saveLiner);
break;
}
@@ -258,7 +254,7 @@ namespace Textamina.Markdig.Parsing
processLiner = false;
if (result != MatchLineResult.LastDiscard && result != MatchLineResult.ContinueDiscard)
{
leaf.Append(line);
leaf.Lines.Append(ref blockParserState.Line);
}
if (blockParserState.NewBlocks.Count > 0)
@@ -294,12 +290,12 @@ namespace Textamina.Markdig.Parsing
private void ParseNewBlocks(ref bool continueProcessLiner)
{
blockParserState.Reset(line);
blockParserState.Pending = null;
for (int j = 0; j < blockParsers.Count; j++)
{
var blockParser = blockParsers[j];
if (line.IsEol)
if (blockParserState.Line.IsEndOfSlice)
{
continueProcessLiner = false;
break;
@@ -319,19 +315,19 @@ namespace Textamina.Markdig.Parsing
blockParserState.CurrentContainer = LastContainer;
blockParserState.LastBlock = lastBlock;
var saveLiner = line.Save();
var saveLiner = blockParserState.Line;
var result = blockParser.Match(blockParserState);
if (result == MatchLineResult.None)
{
// If we have reached a blank line after trying to parse a paragraph
// we can ignore it
if (isParsingParagraph && line.IsBlankLine())
if (isParsingParagraph && blockParserState.IsBlankLine)
{
continueProcessLiner = false;
break;
}
line.Restore(ref saveLiner);
blockParserState.SetCurrentLine(ref saveLiner);
continue;
}
@@ -342,7 +338,7 @@ namespace Textamina.Markdig.Parsing
Debug.Assert(blockParserState.NewBlocks.Count == 0);
continueProcessLiner = false;
paragraph.Append(line);
paragraph.Lines.Append(ref blockParserState.Line);
// We have just found a lazy continuation for a paragraph, early exit
// Mark all block opened after a lazy continuation
@@ -383,7 +379,7 @@ namespace Textamina.Markdig.Parsing
{
var block = newBlocks.Pop();
block.Line = line.LineIndex;
block.Line = blockParserState.LineIndex;
// If we have a leaf block
var leaf = block as LeafBlock;
@@ -391,7 +387,7 @@ namespace Textamina.Markdig.Parsing
{
if (result != MatchLineResult.LastDiscard && result != MatchLineResult.ContinueDiscard)
{
leaf.Append(line);
leaf.Lines.Append(ref blockParserState.Line);
}
if (newBlocks.Count > 0)
@@ -433,28 +429,25 @@ namespace Textamina.Markdig.Parsing
var lines = leafBlock.Lines;
leafBlock.Inline = new ContainerInline() {IsClosed = false};
var inlineState = new InlineParserState(stringBuilderCache, document);
inlineState.Lines = lines;
inlineState.Inline = leafBlock.Inline;
inlineState.Block = leafBlock;
var saveLines = new StringLineGroup.State();
while (!lines.IsEndOfLines)
var inlineState = new InlineParserState(stringBuilderCache, document)
{
lines.Save(ref saveLines);
Text = new StringSlice(leafBlock.Lines.ToString()),
Inline = leafBlock.Inline,
Block = leafBlock
};
var c = lines.CurrentChar;
while (!inlineState.Text.IsEndOfSlice)
{
var saveLine = inlineState.Text;
var c = saveLine.CurrentChar;
var inlineParser = c < 128 ? inlineWithFirstCharParsers[c] : null;
if (inlineParser == null || !inlineParser.Match(inlineState))
{
for (int i = 0; i < regularInlineParsers.Count; i++)
{
lines.Restore(ref saveLines);
inlineState.Text = saveLine;
inlineParser = regularInlineParsers[i];
if (inlineParser.Match(inlineState))
{
break;
@@ -465,7 +458,7 @@ namespace Textamina.Markdig.Parsing
if (inlineParser == null)
{
lines.Restore(ref saveLines);
inlineState.Text = saveLine;
}
}

View File

@@ -1,5 +1,3 @@
using System;
using Textamina.Markdig.Helpers;
using Textamina.Markdig.Parsing;
namespace Textamina.Markdig.Syntax
@@ -28,56 +26,15 @@ namespace Textamina.Markdig.Syntax
public override MatchLineResult Match(BlockParserState state)
{
var liner = state.Line;
int position = liner.Start;
liner.SkipLeadingSpaces3();
// 4.4 Indented code blocks
var c = liner.Current;
var isTab = c.IsTab();
var isSpace = c.IsSpace();
var isBlankLine = liner.IsBlankLine();
var codeBlock = state.Pending as CodeBlock;
// && !isBlankLine) || (isBlankLine && codeBlock != null && !codeBlock.Lines[codeBlock.Lines.Count - 1].IsBlankLine())
if ((codeBlock != null && isBlankLine) || (isTab || (isSpace && (liner.Start - position) == 3)))
//if (((isTab || (isSpace && (liner.Start - position) == 3)) && !isBlankLine))
if (!state.IsCodeIndent || state.IsBlankLine)
{
liner.NextChar();
if (state.Pending == null)
{
if (isBlankLine)
{
return MatchLineResult.None;
}
state.NewBlocks.Push(new CodeBlock(this) { Column = position });
}
return MatchLineResult.Continue;
return state.IsBlankLine && state.Pending != null ? MatchLineResult.LastDiscard : MatchLineResult.None;
}
return MatchLineResult.None;
}
public override void Close(BlockParserState state)
{
var codeBlock = state.Pending as CodeBlock;
if (codeBlock != null)
if (state.Pending == null)
{
var lines = codeBlock.Lines;
// Remove trailing blankline
for (int i = lines.Count - 1; i >= 0; i--)
{
if (lines[i].IsBlankLine())
{
lines.RemoveAt(i);
}
else
{
break;
}
}
state.NewBlocks.Push(new CodeBlock(this) { Column = state.Column });
}
return MatchLineResult.Continue;
}
}
}

View File

@@ -34,149 +34,148 @@ namespace Textamina.Markdig.Syntax
{
public override MatchLineResult Match(BlockParserState state)
{
var liner = state.Line;
int count;
char matchChar;
char c = state.CurrentChar;
int offset = 0;
var fenced = state.Pending as FencedCodeBlock;
if (fenced != null)
var currentFenced = state.Pending as FencedCodeBlock;
if (currentFenced != null)
{
var saveLiner = liner.Save();
liner.SkipLeadingSpaces3();
var c = liner.Current;
int count = fenced.fencedCharCount;
var matchChar = fenced.fencedChar;
while (!liner.IsEol)
count = currentFenced.fencedCharCount;
matchChar = currentFenced.fencedChar;
while (c == matchChar)
{
if (c != matchChar)
{
break;
}
c = liner.NextChar();
count--;
offset++;
c = state.Line.PeekChar(offset);
}
if (count <= 0)
if (offset >= count)
{
int endPosition = liner.Start;
liner.TrimEnd(true);
if (liner.IsEol || liner.End == endPosition)
state.Line.TrimEnd(true);
if (state.CurrentChar == matchChar)
{
return MatchLineResult.LastDiscard;
}
}
liner.Restore(ref saveLiner);
// TODO: It is unclear how to handle this correctly
// Break only if Eof
return MatchLineResult.Continue;
}
else
{
// TODO: We need to count the number of leading space to remove them on each line
var start = liner.Start;
liner.SkipLeadingSpaces3();
var column = liner.Start;
var indentCount = liner.Start - start;
var c = liner.Current;
int count = 0;
var matchChar = (char)0;
while (!liner.IsEol)
// Else if the we have an indent, it is not valid
if (state.IsCodeIndent)
{
return MatchLineResult.None;
}
count = 0;
matchChar = (char) 0;
while (c != '\0')
{
if (count == 0 && (c == '`' || c == '~'))
{
if (count == 0 && (c == '`' || c == '~'))
{
matchChar = c;
}
else if (c != matchChar)
{
break;
}
c = liner.NextChar();
count++;
matchChar = c;
}
else if (c != matchChar)
{
break;
}
count++;
c = state.PeekChar(count);
}
if (count >= 3)
{
return MatchLineResult.None;
}
// TODO: We need to count the number of leading space to remove them on each line
var column = state.Column;
// specs spaces: Is space and tabs? or only spaces? Use space and tab for this case
while (c.IsSpaceOrTab())
{
offset++;
c = state.PeekChar(offset);
}
var start = state.Start + count + offset;
string infoString;
string argString = null;
// An info string cannot contain any backsticks
int firstSpace = -1;
for (int i = start; i <= state.EndOffset; i++)
{
c = state.Line[i];
if (c == '`')
{
return MatchLineResult.None;
}
if (count >= 3)
if (firstSpace < 0 && c.IsSpaceOrTab())
{
// specs spaces: Is space and tabs? or only spaces? Use space and tab for this case
liner.TrimStart(true);
liner.TrimEnd(true);
firstSpace = i;
}
}
string infoString = null;
string argString = null;
if (firstSpace > 0)
{
infoString = state.Line.Text.Substring(start, firstSpace - start);
// An info string cannot contain any backsticks
int firstSpace = -1;
for (int i = liner.Start; i <= liner.End; i++)
// Skip any spaces after info string
firstSpace++;
while (true)
{
c = state.Line[firstSpace];
if (c.IsSpaceOrTab())
{
c = liner[i];
if (c == '`')
{
return MatchLineResult.None;
}
else if (firstSpace < 0 && c.IsSpaceOrTab())
{
firstSpace = i;
}
}
if (firstSpace > 0)
{
infoString = liner.Text.Substring(liner.Start, firstSpace - liner.Start);
// Skip any spaces after info string
firstSpace++;
while (true)
{
c = liner[firstSpace];
if (c.IsSpaceOrTab())
{
firstSpace++;
}
else
{
break;
}
}
argString = liner.Text.Substring(firstSpace, liner.End - firstSpace + 1);
}
else
{
infoString = liner.ToString();
break;
}
// Store the number of matched string into the context
state.NewBlocks.Push(new FencedCodeBlock(this)
{
Column = column,
fencedChar = matchChar,
fencedCharCount = count,
indentCount = indentCount,
Language = HtmlHelper.Unescape(infoString),
Arguments = HtmlHelper.Unescape(argString),
});
return MatchLineResult.ContinueDiscard;
}
return MatchLineResult.None;
argString = state.Line.Text.Substring(firstSpace, state.Line.End - firstSpace + 1);
}
else
{
infoString = state.Line.Text.Substring(start, state.EndOffset - start + 1);
}
// Store the number of matched string into the context
state.NewBlocks.Push(new FencedCodeBlock(this)
{
Column = column,
fencedChar = matchChar,
fencedCharCount = count,
indentCount = state.Indent,
Language = HtmlHelper.Unescape(infoString),
Arguments = HtmlHelper.Unescape(argString),
});
// Discard the current line
return MatchLineResult.ContinueDiscard;
}
public override void Close(BlockParserState state)
{
var fenced = (FencedCodeBlock) state.Pending;
for (int i = 0; i < fenced.Lines.Count; i++)
var fenced = ((FencedCodeBlock) state.Pending);
var lines = fenced.Lines;
for (int i = 0; i < lines.Count; i++)
{
var line = fenced.Lines[i];
// Fences can be indented. If the opening fence is indented,
// content lines will have equivalent opening indentation removed, if present:
for (int j = 0; j < fenced.indentCount; j++)
{
if (line.Start < line.End && line[line.Start].IsSpace())
var start = lines.Slices[i].Start;
if (start < lines.Slices[i].End && lines.Slices[i][start].IsSpace())
{
line.Start++;
lines.Slices[i].Start++;
}
else
{

View File

@@ -20,10 +20,10 @@ namespace Textamina.Markdig.Syntax
{
public override MatchLineResult Match(BlockParserState state)
{
var liner = state.Line;
liner.SkipLeadingSpaces3();
var column = liner.Start;
if (state.IsCodeIndent)
{
return MatchLineResult.None;
}
// 4.2 ATX headings
// An ATX heading consists of a string of characters, parsed as inline content,
@@ -35,37 +35,37 @@ namespace Textamina.Markdig.Syntax
// the heading are stripped of leading and trailing spaces before being parsed as
// inline content. The heading level is equal to the number of # characters in the
// opening sequence.
var c = liner.Current;
var column = state.Column;
var c = state.CurrentChar;
int leadingCount = 0;
for (; !liner.IsEol && leadingCount <= 6; leadingCount++)
for (; !state.Line.IsEndOfSlice && leadingCount <= 6; leadingCount++)
{
if (c != '#')
{
break;
}
c = liner.NextChar();
c = state.PeekChar(leadingCount);
}
// closing # will be handled later, because anyway we have matched
// A space is required after leading #
if (leadingCount > 0 && leadingCount <=6 && (c.IsSpace() || liner.IsEol))
if (leadingCount > 0 && leadingCount <=6 && (c.IsSpace() || state.Line.IsEndOfSlice))
{
liner.NextChar();
state.Line.Start = leadingCount + 1;
state.NewBlocks.Push(new HeadingBlock(this) {Level = leadingCount, Column = column });
// The optional closing sequence of #s must be preceded by a space and may be followed by spaces only.
int endState = 0;
int countClosingTags = 0;
for (int i = liner.End; i >= liner.Start - 1; i--) // Go up to Start - 1 in order to match the space after the first ###
for (int i = state.Line.End; i >= state.Line.Start - 1; i--) // Go up to Start - 1 in order to match the space after the first ###
{
c = liner[i];
c = state.Line[i];
if (endState == 0)
{
if (CharHelper.IsSpace(c)) // TODO: Not clear if it is a space or space+tab in the specs
if (c.IsSpace()) // TODO: Not clear if it is a space or space+tab in the specs
{
continue;
}
@@ -81,9 +81,9 @@ namespace Textamina.Markdig.Syntax
if (countClosingTags > 0)
{
if (CharHelper.IsSpace(c))
if (c.IsSpace())
{
liner.End = i - 1;
state.Line.End = i - 1;
}
break;
}

View File

@@ -16,10 +16,6 @@ namespace Textamina.Markdig.Syntax
public HtmlBlockType Type { get; set; }
[ThreadStatic]
private static readonly StringLineGroup HtmlLineGroup = new StringLineGroup();
private class ParserInternal : BlockParser
{
private static readonly string[] HtmlTags =
@@ -106,12 +102,11 @@ namespace Textamina.Markdig.Syntax
private MatchLineResult MatchStart(BlockParserState state)
{
var liner = state.Line;
int index = 0;
for (int i = 0; i < 3; i++)
{
if (!liner.PeekChar(index).IsSpace())
if (!state.Line.PeekChar(index).IsSpace())
{
break;
}
@@ -120,36 +115,34 @@ namespace Textamina.Markdig.Syntax
// Early exit if it is not starting by an HTML tag
var column = index;
var c = liner.PeekChar(index++);
var c = state.Line.PeekChar(index++);
if (c != '<')
{
return MatchLineResult.None;
}
var result = TryParseTagType16(state, liner, index, column);
var result = TryParseTagType16(state, ref state.Line, index, column);
// HTML blocks of type 7 cannot interrupt a paragraph:
if (result == MatchLineResult.None && !(state.LastBlock is ParagraphBlock))
{
result = TryParseTagType7(state, liner, index, column);
result = TryParseTagType7(state, ref state.Line, index, column);
}
return result;
}
private MatchLineResult TryParseTagType7(BlockParserState state, StringLine liner, int index, int startColumn)
private MatchLineResult TryParseTagType7(BlockParserState state, ref StringSlice liner, int index, int startColumn)
{
var builder = StringBuilderCache.Local();
var text = HtmlLineGroup;
text.Add(liner);
text.SetColumn(index);
var c = text.CurrentChar;
liner.Start = index;
var c = liner.CurrentChar;
var result = MatchLineResult.None;
if ((c == '/' && HtmlHelper.TryParseHtmlCloseTag(text, builder)) || HtmlHelper.TryParseHtmlTagOpenTag(text, builder))
if ((c == '/' && HtmlHelper.TryParseHtmlCloseTag(liner, builder)) || HtmlHelper.TryParseHtmlTagOpenTag(liner, builder))
{
// Must be followed by whitespace only
bool hasOnlySpaces = true;
c = text.CurrentChar;
c = liner.CurrentChar;
while (true)
{
if (c == '\0')
@@ -161,7 +154,7 @@ namespace Textamina.Markdig.Syntax
hasOnlySpaces = false;
break;
}
c = text.NextChar();
c = liner.NextChar();
}
if (hasOnlySpaces)
@@ -170,12 +163,11 @@ namespace Textamina.Markdig.Syntax
}
}
text.Clear();
builder.Clear();
return result;
}
private MatchLineResult TryParseTagType16(BlockParserState state, StringLine liner, int index, int startColumn)
private MatchLineResult TryParseTagType16(BlockParserState state, ref StringSlice liner, int index, int startColumn)
{
char c;
c = liner.PeekChar(index);
@@ -255,51 +247,49 @@ namespace Textamina.Markdig.Syntax
private MatchLineResult MatchEnd(BlockParserState state, HtmlBlock htmlBlock)
{
var liner = state.Line;
// Early exit if it is not starting by an HTML tag
var c = liner.Current;
var c = state.Line.CurrentChar;
switch (htmlBlock.Type)
{
case HtmlBlockType.Comment:
if (liner.Search("-->"))
if (state.Line.Search("-->"))
{
return MatchLineResult.Last;
}
break;
case HtmlBlockType.CData:
if (liner.Search("]]>"))
if (state.Line.Search("]]>"))
{
return MatchLineResult.Last;
}
break;
case HtmlBlockType.ProcessingInstruction:
if (liner.Search("?>"))
if (state.Line.Search("?>"))
{
return MatchLineResult.Last;
}
break;
case HtmlBlockType.DocumentType:
if (liner.Search(">"))
if (state.Line.Search(">"))
{
return MatchLineResult.Last;
}
break;
case HtmlBlockType.ScriptPreOrStyle:
// TODO: could be optimized with a dedicated parser
if (liner.SearchLowercase("</script>") || liner.SearchLowercase("</pre>") || liner.SearchLowercase("</style>"))
if (state.Line.SearchLowercase("</script>") || state.Line.SearchLowercase("</pre>") || state.Line.SearchLowercase("</style>"))
{
return MatchLineResult.Last;
}
break;
case HtmlBlockType.InterruptingBlock:
if (liner.IsBlankLine())
if (state.IsBlankLine)
{
return MatchLineResult.LastDiscard;
}
break;
case HtmlBlockType.NonInterruptingBlock:
if (liner.IsBlankLine())
if (state.IsBlankLine)
{
return MatchLineResult.LastDiscard;
}

View File

@@ -20,20 +20,18 @@ namespace Textamina.Markdig.Syntax
public override bool Match(InlineParserState state)
{
var text = state.Lines;
string link;
bool isEmail;
var saved = text.Save();
if (LinkHelper.TryParseAutolink(text, out link, out isEmail))
var saved = state.Text;
if (LinkHelper.TryParseAutolink(ref state.Text, out link, out isEmail))
{
state.Inline = new AutolinkInline() {IsEmail = isEmail, Url = link};
}
else
{
text.Restore(ref saved);
state.Text = saved;
string htmlTag;
if (!HtmlHelper.TryParseHtmlTag(text, out htmlTag))
if (!HtmlHelper.TryParseHtmlTag(ref state.Text, out htmlTag))
{
return false;
}

View File

@@ -18,10 +18,10 @@ namespace Textamina.Markdig.Syntax
public override bool Match(InlineParserState state)
{
var text = state.Lines;
var text = state.Text;
int openSticks = 0;
if (text.PreviousChar1 == '`')
if (text.PeekChar(-1) == '`')
{
return false;
}

View File

@@ -215,15 +215,13 @@ namespace Textamina.Markdig.Syntax
public override bool Match(InlineParserState state)
{
var lines = state.Lines;
// First, some definitions. A delimiter run is either a sequence of one or more * characters that
// is not preceded or followed by a * character, or a sequence of one or more _ characters that
// is not preceded or followed by a _ character.
var delimiterChar = lines.CurrentChar;
var pc = lines.PreviousChar1;
if (delimiterChar == pc && lines.PreviousChar2 != '\\')
var delimiterChar = state.Text.CurrentChar;
var pc = state.Text.PeekChar(-1);
if (delimiterChar == pc && state.Text.PeekChar(-2) != '\\')
{
return false;
}
@@ -233,7 +231,7 @@ namespace Textamina.Markdig.Syntax
do
{
delimiterCount++;
c = lines.NextChar();
c = state.Text.NextChar();
} while (c == delimiterChar);

View File

@@ -20,7 +20,7 @@ namespace Textamina.Markdig.Syntax
public override bool Match(InlineParserState state)
{
var lines = state.Lines;
var lines = state.Text;
// Go to escape character
lines.NextChar();

View File

@@ -17,8 +17,7 @@ namespace Textamina.Markdig.Syntax
public override bool Match(InlineParserState state)
{
// Hard line breaks are for separating inline content within a block. Neither syntax for hard line breaks works at the end of a paragraph or other block element:
var text = state.Lines;
if (!(state.Block is ParagraphBlock) || text.ColumnPosition == 0 || !text.PreviousChar1.IsSpace() || !text.PreviousChar2.IsSpace())
if (!(state.Block is ParagraphBlock) || state.Text.Column == 0 || !state.Text.PeekChar(-1).IsSpace() || !state.Text.PeekChar(-2).IsSpace())
{
return false;
}

View File

@@ -30,15 +30,13 @@ namespace Textamina.Markdig.Syntax
public override bool Match(InlineParserState state)
{
var text = state.Lines;
var c = text.CurrentChar;
var c = state.Text.CurrentChar;
bool isImage = false;
if (c == '!')
{
isImage = true;
c = text.NextChar();
c = state.Text.NextChar();
if (c != '[')
{
return false;
@@ -50,21 +48,21 @@ namespace Textamina.Markdig.Syntax
case '[':
// If this is not an image, we may have a reference link shortcut
// so we try to resolve it here
var saved = text.Save();
var saved = state.Text;
string label;
// If the label is followed by either a ( or a [, this is not a shortcut
if (LinkHelper.TryParseLabel(text, out label))
if (LinkHelper.TryParseLabel(ref state.Text, out label))
{
if (!state.Document.LinkReferenceDefinitions.ContainsKey(label))
{
label = null;
}
}
text.Restore(ref saved);
state.Text = saved;
// Else we insert a LinkDelimiter
text.NextChar();
state.Text.NextChar();
state.Inline = new LinkDelimiterInline(this)
{
Type = DelimiterType.Open,
@@ -74,10 +72,10 @@ namespace Textamina.Markdig.Syntax
return true;
case ']':
text.NextChar();
state.Text.NextChar();
if (state.Inline != null)
{
if (TryProcessLinkOrImage(state, text))
if (TryProcessLinkOrImage(state, ref state.Text))
{
return true;
}
@@ -146,7 +144,7 @@ namespace Textamina.Markdig.Syntax
return isValidLink;
}
private bool TryProcessLinkOrImage(InlineParserState inlineState, StringLineGroup text)
private bool TryProcessLinkOrImage(InlineParserState inlineState, ref StringSlice text)
{
LinkDelimiterInline openParent = null;
foreach (var parent in inlineState.Inline.FindParentOfType<LinkDelimiterInline>())
@@ -164,7 +162,7 @@ namespace Textamina.Markdig.Syntax
case '(':
string url;
string title;
if (LinkHelper.TryParseInlineLink(text, out url, out title))
if (LinkHelper.TryParseInlineLink(ref text, out url, out title))
{
// Inline Link
var link = new LinkInline()
@@ -192,7 +190,7 @@ namespace Textamina.Markdig.Syntax
// Handle Collapsed links
if (text.CurrentChar == '[')
{
if (text.PeekCharOnSameLine() == ']')
if (text.PeekChar(1) == ']')
{
label = openParent.Label;
text.NextChar(); // Skip [
@@ -204,7 +202,7 @@ namespace Textamina.Markdig.Syntax
label = openParent.Label;
}
if (label != null || LinkHelper.TryParseLabel(text, true, out label))
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label))
{
if (ProcessLinkReference(inlineState, label, openParent.IsImage,
openParent.FirstChild))

View File

@@ -41,7 +41,7 @@ namespace Textamina.Markdig.Syntax
builder = literal.ContentBuilder;
}
var text = state.Lines;
var text = state.Text;
builder.Append(text.CurrentChar);
text.NextChar();
return true;

View File

@@ -6,18 +6,13 @@ namespace Textamina.Markdig.Syntax
{
protected LeafBlock(BlockParser parser) : base(parser)
{
Lines = new StringLineGroup();
Lines = new StringSliceList();
}
public StringLineGroup Lines { get; set; }
public StringSliceList Lines { get; set; }
public Inline Inline { get; set; }
public bool NoInline { get; set; }
internal void Append(StringLine line)
{
Lines.Add(line);
}
}
}

View File

@@ -23,14 +23,14 @@ namespace Textamina.Markdig.Syntax
public string Title { get; set; }
public static bool TryParse(StringLineGroup lines, out LinkReferenceDefinitionBlock block)
public static bool TryParse(ref StringSlice text, out LinkReferenceDefinitionBlock block)
{
block = null;
string label;
string url;
string title;
if (!LinkHelper.TryParseLinkReferenceDefinition(lines, out label, out url, out title))
if (!LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title))
{
return false;
}

View File

@@ -38,26 +38,24 @@ namespace Textamina.Markdig.Syntax
return MatchLineResult.Skip;
}
var liner = state.Line;
// When both a thematic break and a list item are possible
// interpretations of a line, the thematic break takes precedence
var save = liner.Save();
if (BreakBlock.Parser.Match(state) == MatchLineResult.Last)
var save = state.Line;
if (ThematicBreakBlock.Parser.Match(state) == MatchLineResult.Last)
{
// Remove the BreakBlock as we will let the BreakBlock to catch it later
// Remove the ThematicBreakBlock as we will let the ThematicBreakBlock to catch it later
state.NewBlocks.Pop();
return MatchLineResult.None;
}
liner.Restore(ref save);
state.SetCurrentLine(ref save);
// 5.2 List items
// TODO: Check with specs, it is not clear that list marker or bullet marker must be followed by at least 1 space
int preIndent = 0;
for (int i = liner.Start - 1; i >= 0; i--)
for (int i = state.Line.Start - 1; i >= 0; i--)
{
if (liner[i].IsSpaceOrTab())
if (state.Line[i].IsSpaceOrTab())
{
preIndent++;
}
@@ -67,7 +65,7 @@ namespace Textamina.Markdig.Syntax
}
}
var saveLiner = liner.Save();
var saveLiner = state.Line;
// If we have already a ListItemBlock, we are going to try to append to it
var listItem = state.Pending as ListItemBlock;
@@ -78,7 +76,7 @@ namespace Textamina.Markdig.Syntax
// Allow all blanks lines if the last block is a fenced code block
// Allow 1 blank line inside a list
// If > 1 blank line, terminate this list
var isBlankLine = liner.IsBlankLine();
var isBlankLine = state.IsBlankLine;
//if (isBlankLine && !(state.LastBlock is FencedCodeBlock)) // TODO: Handle this case
var isInFencedBlock = state.LastBlock is FencedCodeBlock;
if (isBlankLine)
@@ -114,26 +112,26 @@ namespace Textamina.Markdig.Syntax
list.CountBlankLinesReset = 0;
var c = liner.Current;
var startPosition = liner.Column;
var c = state.Line.CurrentChar;
var startPosition = state.Line.Start;
// List Item starting with a blank line (-1)
if (listItem.NumberOfSpaces < 0)
{
int expectedCount = -listItem.NumberOfSpaces;
int countSpaces = 0;
var saved = new StringLine.State();
var saved = new StringSlice();
while (c.IsSpaceOrTab())
{
c = liner.NextChar();
countSpaces = preIndent + liner.Column - startPosition;
c = state.Line.NextChar();
countSpaces = preIndent + state.Line.Column - startPosition;
if (countSpaces == expectedCount)
{
saved = liner.Save();
saved = state.Line;
}
else if (countSpaces >= 4)
{
liner.Restore(ref saved);
state.SetCurrentLine(ref saved);
countSpaces = expectedCount;
break;
}
@@ -149,15 +147,15 @@ namespace Textamina.Markdig.Syntax
{
while (c.IsSpaceOrTab())
{
c = liner.NextChar();
var countSpaces = preIndent + liner.Column - startPosition;
c = state.Line.NextChar();
var countSpaces = preIndent + state.Line.Column - startPosition;
if (countSpaces >= listItem.NumberOfSpaces)
{
return MatchLineResult.Continue;
}
}
}
liner.Restore(ref saveLiner);
state.SetCurrentLine(ref saveLiner);
}
return TryParseListItem(ref state, preIndent);
@@ -165,33 +163,32 @@ namespace Textamina.Markdig.Syntax
private MatchLineResult TryParseListItem(ref BlockParserState state, int preIndent)
{
var liner = state.Line;
var isInList = state.Pending is ListItemBlock;
var preStartPosition = liner.Start;
var preStartPosition = state.Line.Start;
var c = liner.Current;
var c = state.Line.CurrentChar;
if (isInList)
{
while (c.IsSpaceOrTab())
{
c = liner.NextChar();
c = state.Line.NextChar();
}
}
else
{
liner.SkipLeadingSpaces3();
c = liner.Current;
// TODO
//state.Line.SkipLeadingSpaces3();
c = state.Line.CurrentChar;
}
preIndent = preIndent + liner.Start - preStartPosition;
preIndent = preIndent + state.Line.Start - preStartPosition;
var isOrdered = false;
var bulletChar = (char) 0;
int orderedStart = 0;
var orderedDelimiter = (char) 0;
var column = liner.Start;
var column = state.Line.Start;
if (c.IsBulletListMarker())
{
@@ -204,7 +201,7 @@ namespace Textamina.Markdig.Syntax
while (c.IsDigit())
{
orderedStart = orderedStart*10 + c - '0';
c = liner.NextChar();
c = state.Line.NextChar();
preIndent++;
countDigit++;
}
@@ -230,11 +227,11 @@ namespace Textamina.Markdig.Syntax
}
// Skip Bullet or '.'
liner.NextChar();
state.Line.NextChar();
// Item starting with a blank line
int numberOfSpaces;
if (liner.IsBlankLine())
if (state.IsBlankLine)
{
// Use a negative number to store the number of expected chars
numberOfSpaces = -(preIndent + 1);
@@ -243,34 +240,34 @@ namespace Textamina.Markdig.Syntax
{
var startPosition = -1;
int countSpaceAfterBullet = 0;
var saved = new StringLine.State();
var saved = new StringSlice();
for (int i = 0; i <= 4; i++)
{
c = liner.Current;
c = state.Line.CurrentChar;
if (!c.IsSpaceOrTab())
{
break;
}
if (i == 0)
{
startPosition = liner.Column;
startPosition = state.Line.Column;
}
var endPosition = liner.Column;
var endPosition = state.Line.Column;
countSpaceAfterBullet = endPosition - startPosition;
if (countSpaceAfterBullet == 1)
{
saved = liner.Save();
saved = state.Line;
}
else if (countSpaceAfterBullet >= 4)
{
liner.SpaceHeaderCount = countSpaceAfterBullet - 4;
//state.Line.SpaceHeaderCount = countSpaceAfterBullet - 4;
countSpaceAfterBullet = 0;
liner.Restore(ref saved);
state.SetCurrentLine(ref saved);
break;
}
liner.NextChar();
state.Line.NextChar();
}
// If we haven't matched any spaces, early exit

View File

@@ -25,13 +25,15 @@ namespace Textamina.Markdig.Syntax
{
public override MatchLineResult Match(BlockParserState state)
{
var liner = state.Line;
liner.SkipLeadingSpaces3();
if (state.IsCodeIndent)
{
return MatchLineResult.None;
}
var column = liner.Start;
var column = state.Column;
// Else it is a continue, we don't break on blank lines
var isBlankLine = liner.IsBlankLine();
var isBlankLine = state.IsBlankLine;
var paragraph = state.Pending as ParagraphBlock;
@@ -59,9 +61,9 @@ namespace Textamina.Markdig.Syntax
{
var headingChar = (char) 0;
bool checkForSpaces = false;
for (int i = liner.Start; i <= liner.End; i++)
for (int i = state.Start; i <= state.EndOffset; i++)
{
var c = liner[i];
var c = state.Line[i];
if (headingChar == 0)
{
if (c == '=' || c == '-')
@@ -112,10 +114,7 @@ namespace Textamina.Markdig.Syntax
Level = level,
Lines = paragraph.Lines,
};
foreach (var line in heading.Lines)
{
line.Trim();
}
heading.Lines.Trim();
// Remove the paragraph as a pending block
state.NewBlocks.Push(heading);
@@ -129,46 +128,46 @@ namespace Textamina.Markdig.Syntax
return result;
}
private bool TryMatchLinkReferenceDefinition(StringLineGroup localLineGroup, BlockParserState state)
private bool TryMatchLinkReferenceDefinition(StringSliceList localLineGroup, BlockParserState state)
{
bool atLeastOneFound = false;
var saved = new StringLineGroup.State();
while (true)
{
// If we have found a LinkReferenceDefinition, we can discard the previous paragraph
localLineGroup.Save(ref saved);
LinkReferenceDefinitionBlock linkReferenceDefinition;
if (LinkReferenceDefinitionBlock.TryParse(localLineGroup, out linkReferenceDefinition))
{
if (!state.Root.LinkReferenceDefinitions.ContainsKey(linkReferenceDefinition.Label))
{
state.Root.LinkReferenceDefinitions[linkReferenceDefinition.Label] = linkReferenceDefinition;
}
atLeastOneFound = true;
//var saved = new StringSliceList.State();
//while (true)
//{
// // If we have found a LinkReferenceDefinition, we can discard the previous paragraph
// localLineGroup.Save(ref saved);
// LinkReferenceDefinitionBlock linkReferenceDefinition;
// if (LinkReferenceDefinitionBlock.TryParse(localLineGroup, out linkReferenceDefinition))
// {
// if (!state.Root.LinkReferenceDefinitions.ContainsKey(linkReferenceDefinition.Label))
// {
// state.Root.LinkReferenceDefinitions[linkReferenceDefinition.Label] = linkReferenceDefinition;
// }
// atLeastOneFound = true;
// Remove lines that have been matched
if (localLineGroup.LinePosition == localLineGroup.Count)
{
localLineGroup.Clear();
}
else
{
for (int i = localLineGroup.LinePosition - 1; i >= 0; i--)
{
localLineGroup.RemoveAt(i);
}
}
}
else
{
if (!atLeastOneFound)
{
localLineGroup.Restore(ref saved);
}
break;
}
}
// // Remove lines that have been matched
// if (localLineGroup.LinePosition == localLineGroup.Count)
// {
// localLineGroup.Clear();
// }
// else
// {
// for (int i = localLineGroup.LinePosition - 1; i >= 0; i--)
// {
// localLineGroup.RemoveAt(i);
// }
// }
// }
// else
// {
// if (!atLeastOneFound)
// {
// localLineGroup.Restore(ref saved);
// }
// break;
// }
//}
return atLeastOneFound;
}
@@ -193,7 +192,7 @@ namespace Textamina.Markdig.Syntax
var lineCount = lines.Count;
for (int i = 0; i < lineCount; i++)
{
var line = lines[i];
var line = lines.Slices[i];
line.Trim();
}
}

View File

@@ -15,17 +15,18 @@ namespace Textamina.Markdig.Syntax
{
public override MatchLineResult Match(BlockParserState state)
{
var liner = state.Line;
liner.SkipLeadingSpaces3();
if (state.IsCodeIndent)
{
return MatchLineResult.None;
}
// 5.1 Block quotes
// A block quote marker consists of 0-3 spaces of initial indent, plus (a) the character > together with a following space, or (b) a single character > not followed by a space.
var c = liner.Current;
var column = liner.Start;
var c = state.CurrentChar;
var column = state.Column;
if (c != '>')
{
if (state.Pending != null && liner.IsBlankLine())
if (state.Pending != null && state.IsBlankLine)
{
state.Close(state.Pending);
return MatchLineResult.None;
@@ -33,10 +34,10 @@ namespace Textamina.Markdig.Syntax
return MatchLineResult.None;
}
c = liner.NextChar();
c = state.NextChar();
if (c.IsSpace())
{
liner.NextChar();
state.NextChar();
}
if (state.Pending == null)

View File

@@ -1,251 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using Textamina.Markdig.Helpers;
using Textamina.Markdig.Parsing;
namespace Textamina.Markdig.Syntax
{
public sealed class StringLine
{
public StringLine(string text, int lineIndex = 0)
{
if (text == null) throw new ArgumentNullException(nameof(text));
LineIndex = lineIndex;
Text = text;
Start = 0;
End = Text.Length - 1;
Column = 0;
SpaceHeaderCount = 0;
Current = End >= 0 ? Text[0] : (char)0;
}
public int LineIndex { get; }
public string Text { get; private set; }
public int Start { get; set; }
public int End { get; set; }
public int SpaceHeaderCount { get; set; }
public int Column { get; private set; }
public char Current { get; private set; }
public bool IsEol => Start > End;
private bool isBlankLine;
public char this[int index] => Text[index];
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public State Save()
{
return new State(Start, End, Column, SpaceHeaderCount, Current);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public void Restore(State state)
{
Restore(ref state);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public void Restore(ref State state)
{
Start = state.Start;
End = state.End;
Column = state.Column;
SpaceHeaderCount = state.SpaceHeaderCount;
Current = state.Current;
}
public char NextChar()
{
Start++;
if (Start <= End)
{
// If previous character was a tab make the Column += 4
Column++;
if (Current.IsTab())
{
// Align the tab on a column
Column = ((Column + 3) / 4) * 4;
}
Current = Text[Start].EscapeInsecure();
}
else
{
Start = End + 1;
Current = (char) 0;
}
return Current;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekChar(int offset)
{
var index = Start + offset;
return index <= End ? Text[index].EscapeInsecure() : (char) 0;
}
public bool Match(string text, int offset = 0)
{
return Match(text, End, offset);
}
public bool Match(string text, int end, int offset)
{
var index = Start + offset;
int i = 0;
for (; index <= end && i < text.Length; i++, index++)
{
if (text[i] != Text[index])
{
return false;
}
}
return i == text.Length;
}
public bool MatchLowercase(string text, int end, int offset)
{
var index = Start + offset;
int i = 0;
for (; index <= end && i < text.Length; i++, index++)
{
if (text[i] != char.ToLowerInvariant(Text[index]))
{
return false;
}
}
return i == text.Length;
}
public bool Search(string text, int offset = 0)
{
var end = End - text.Length + 1;
for (int i = Start; i <= end; i++)
{
if (Match(text, End, i))
{
return true;
}
}
return false;
}
public bool SearchLowercase(string text, int offset = 0)
{
var end = End - text.Length + 1;
for (int i = Start; i <= end; i++)
{
if (MatchLowercase(text, End, i))
{
return true;
}
}
return false;
}
public bool IsBlankLine()
{
if (isBlankLine)
{
return true;
}
for (int i = Start; i <= End; i++)
{
if (!Text[i].IsSpace())
{
return false;
}
}
isBlankLine = true;
return true;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public void SkipLeadingSpaces3()
{
var c = Current;
if (c.IsSpace())
{
c = NextChar();
if (c.IsSpace())
{
c = NextChar();
if (c.IsSpace())
{
NextChar();
}
}
}
}
public StringSlice ToSlice()
{
return new StringSlice(Text, Start, End);
}
public void TrimStart(bool includeTabs = false)
{
// Strip leading spaces
var c = Current;
while (includeTabs ? c.IsSpaceOrTab() :c.IsSpace())
{
c = NextChar();
}
}
public void TrimEnd(bool includeTabs = false)
{
for (int i = End; i >= Start; i--)
{
End = i;
var c = this[i];
if (!(includeTabs ? c.IsSpaceOrTab() : c.IsSpace()))
{
break;
}
}
}
public void Trim()
{
TrimStart();
TrimEnd();
}
public override string ToString()
{
return Start <= End ? Text.Substring(Start, End - Start + 1) : string.Empty;
}
public struct State
{
public State(int start, int end, int column, int spaceHeaderCount, char current)
{
Start = start;
End = end;
Column = column;
SpaceHeaderCount = spaceHeaderCount;
Current = current;
}
public readonly int Start;
public readonly int End;
public readonly int Column;
public readonly int SpaceHeaderCount;
public readonly char Current;
}
}
}

View File

@@ -1,265 +0,0 @@
using System;
using System.Collections.ObjectModel;
using System.Text;
using Textamina.Markdig.Helpers;
using Textamina.Markdig.Parsing;
namespace Textamina.Markdig.Syntax
{
public class StringLineGroup : Collection<StringLine>
{
private StringLine currentLine;
public StringLineGroup()
{
}
public StringLineGroup(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
Add(new StringLine(text));
}
public int LinePosition { get; private set; }
public int ColumnPosition { get; private set; }
public StringLine Current => currentLine;
public char CurrentChar { get; private set; }
public char PreviousChar1 { get; private set; }
public char PreviousChar2 { get; private set; }
public void Reset()
{
ColumnPosition = -1;
LinePosition = 0;
currentLine = Count > 0 ? this[0] : null;
if (currentLine != null)
{
ColumnPosition = currentLine.Start - 1;
}
NextChar();
}
public void SetColumn(int index)
{
if (Current != null && index <= Current.End)
{
CurrentChar = Current[index];
ColumnPosition = index;
// Don't set previous char
PreviousChar1 = '\0';
PreviousChar2 = '\0';
}
}
public char PeekCharOnSameLine()
{
if (Current != null && (ColumnPosition+1) <= Current.End)
{
return Current[ColumnPosition + 1];
}
return (char)0;
}
protected override void ClearItems()
{
base.ClearItems();
Reset();
}
protected override void InsertItem(int index, StringLine item)
{
base.InsertItem(index, item);
if (index == 0)
{
Reset();
}
}
protected override void SetItem(int index, StringLine item)
{
base.SetItem(index, item);
if (index == 0)
{
Reset();
}
}
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
if (index == 0)
{
Reset();
}
}
public bool IsEndOfLines => CurrentChar == '\0';
public State Save()
{
return new State(currentLine, LinePosition, ColumnPosition, CurrentChar, PreviousChar1, PreviousChar2);
}
public void Save(ref State state)
{
state.CurrentLine = currentLine;
state.LinePosition = LinePosition;
state.ColumnPosition = ColumnPosition;
state.Current = CurrentChar;
state.PreviousChar1 = PreviousChar1;
state.PreviousChar2 = PreviousChar2;
}
public void Restore(ref State state)
{
currentLine = state.CurrentLine;
LinePosition = state.LinePosition;
ColumnPosition = state.ColumnPosition;
CurrentChar = state.Current;
PreviousChar1 = state.PreviousChar1;
PreviousChar2 = state.PreviousChar2;
}
public char NextChar()
{
PreviousChar2 = PreviousChar1;
PreviousChar1 = CurrentChar;
if (currentLine != null)
{
ColumnPosition++;
if (ColumnPosition <= currentLine.End)
{
CurrentChar = currentLine[ColumnPosition].EscapeInsecure();
}
else
{
LinePosition++;
if (LinePosition < Count)
{
currentLine = this[LinePosition];
}
else
{
currentLine = null;
LinePosition = Count;
}
ColumnPosition = -1;
if (currentLine != null)
{
ColumnPosition = currentLine.Start - 1;
}
CurrentChar = currentLine != null ? '\n' : '\0';
}
}
else
{
CurrentChar = '\0';
}
return CurrentChar;
}
public bool SkipWhiteSpaces()
{
bool hasWhitespaces = false;
while (CurrentChar.IsWhitespace())
{
NextChar();
hasWhitespaces = true;
}
return hasWhitespaces;
}
public bool SkipWhiteSpaces(out int newLineCount)
{
bool hasWhitespaces = false;
newLineCount = 0;
while (CurrentChar.IsWhitespace())
{
if (CurrentChar == '\n')
{
newLineCount++;
}
NextChar();
hasWhitespaces = true;
}
return hasWhitespaces;
}
public void TrimStart()
{
if (Count == 0)
{
return;
}
// Strip leading and
this[0].TrimStart();
Reset();
}
public void TrimEnd()
{
if (Count == 0)
{
return;
}
this[Count - 1].TrimEnd();
Reset();
}
public void Trim()
{
TrimStart();
TrimEnd();
}
public override string ToString()
{
var stringBuilder = new StringBuilder();
bool firstLine = true;
foreach (var line in this)
{
if (!firstLine)
{
stringBuilder.Append('\n');
}
stringBuilder.Append(line);
firstLine = false;
}
return stringBuilder.ToString();
}
public struct State
{
public State(StringLine currentLine, int linePosition, int columnPosition, char current, char previousChar1, char previousChar2)
{
CurrentLine = currentLine;
LinePosition = linePosition;
ColumnPosition = columnPosition;
Current = current;
PreviousChar1 = previousChar1;
PreviousChar2 = previousChar2;
}
public StringLine CurrentLine;
public int LinePosition;
public int ColumnPosition;
public char Current;
public char PreviousChar1;
public char PreviousChar2;
}
}
}

View File

@@ -1,25 +1,171 @@
namespace Textamina.Markdig.Syntax
using System;
using System.Runtime.CompilerServices;
using Textamina.Markdig.Helpers;
namespace Textamina.Markdig.Syntax
{
public struct StringSlice
{
public StringSlice(string text, int start, int end)
public StringSlice(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
Text = text;
Start = start;
End = end;
Start = 0;
End = Text.Length - 1;
}
public string Text;
public readonly string Text;
public int Start;
public int End;
public int Length
{
get { return End - Start; }
public int Column => Start;
set { End = Start + value; }
public char CurrentChar => Start <= End ? this[Start] : '\0';
public bool IsEndOfSlice => Start > End;
public char this[int index] => Text[index].EscapeInsecure();
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char NextChar()
{
Start++;
if (Start > End)
{
Start = End + 1;
}
return CurrentChar;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekChar(int offset)
{
var index = Start + offset;
return index >= Start && index <= End ? Text[index].EscapeInsecure() : (char) 0;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekCharExtra(int offset)
{
var index = Start + offset;
return index >= 0 && index < Text.Length ? Text[index].EscapeInsecure() : (char)0;
}
public bool Match(string text, int offset = 0)
{
return Match(text, End, offset);
}
public bool Match(string text, int end, int offset)
{
var index = Start + offset;
int i = 0;
for (; index <= end && i < text.Length; i++, index++)
{
if (text[i] != Text[index])
{
return false;
}
}
return i == text.Length;
}
public bool MatchLowercase(string text, int end, int offset)
{
var index = Start + offset;
int i = 0;
for (; index <= end && i < text.Length; i++, index++)
{
if (text[i] != char.ToLowerInvariant(Text[index]))
{
return false;
}
}
return i == text.Length;
}
public bool Search(string text, int offset = 0)
{
var end = End - text.Length + 1;
for (int i = Start; i <= end; i++)
{
if (Match(text, End, i))
{
return true;
}
}
return false;
}
public bool SearchLowercase(string text, int offset = 0)
{
var end = End - text.Length + 1;
for (int i = Start; i <= end; i++)
{
if (MatchLowercase(text, End, i))
{
return true;
}
}
return false;
}
public bool TrimStart()
{
// Strip leading spaces
var c = CurrentChar;
var hasWhitespaces = false;
while (c.IsWhitespace())
{
c = NextChar();
hasWhitespaces = true;
}
return hasWhitespaces;
}
public bool TrimStart(out int newLineCount)
{
bool hasWhitespaces = false;
newLineCount = 0;
var c = CurrentChar;
while (c.IsWhitespace())
{
if (c == '\n')
{
newLineCount++;
}
c = NextChar();
hasWhitespaces = true;
}
return hasWhitespaces;
}
public void TrimEnd(bool includeTabs = false)
{
for (int i = End; i >= Start; i--)
{
End = i;
var c = this[i];
if (!(includeTabs ? c.IsSpaceOrTab() : c.IsSpace()))
{
break;
}
}
}
public void Trim()
{
TrimStart();
TrimEnd();
}
public override string ToString()
{
return Start <= End ? Text.Substring(Start, End - Start + 1) : string.Empty;
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Text;
using Textamina.Markdig.Helpers;
namespace Textamina.Markdig.Syntax
{
public class StringSliceList
{
private static readonly StringSlice[] Empty = new StringSlice[0];
private StringSlice currentLine;
public StringSliceList()
{
Slices = Empty;
}
public StringSliceList(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
Append(new StringSlice(text));
}
public StringSlice[] Slices;
public int Count;
public void Append(ref StringSlice slice)
{
if (Count == Slices.Length) IncreaseCapacity();
Slices[Count++] = slice;
}
public void Append(StringSlice slice)
{
if (Count == Slices.Length) IncreaseCapacity();
Slices[Count++] = slice;
}
private void IncreaseCapacity()
{
int newCapacity = Slices.Length == 0 ? 4 : Slices.Length * 2;
var newItems = new StringSlice[newCapacity];
if (Count > 0)
{
Array.Copy(Slices, 0, newItems, 0, Count);
}
Slices = newItems;
}
public override string ToString()
{
var stringBuilder = StringBuilderCache.Local();
for(int i = 0; i < Count; i++)
{
if (i > 0)
{
stringBuilder.Append('\n');
}
stringBuilder.Append(Slices[i]);
}
var str = stringBuilder.ToString();
stringBuilder.Clear();
return str;
}
}
}

View File

@@ -0,0 +1,14 @@
namespace Textamina.Markdig.Syntax
{
public static class StringSliceListExtensions
{
public static void Trim(this StringSliceList slices)
{
for (int i = 0; i < slices.Count; i++)
{
slices.Slices[i].Trim();
}
}
}
}

View File

@@ -6,11 +6,11 @@ namespace Textamina.Markdig.Syntax
/// <summary>
/// Repressents a thematic break.
/// </summary>
public class BreakBlock : LeafBlock
public class ThematicBreakBlock : LeafBlock
{
public new static readonly BlockParser Parser = new ParserInternal();
public BreakBlock(BlockParser parser) : base(parser)
public ThematicBreakBlock(BlockParser parser) : base(parser)
{
NoInline = true;
}
@@ -19,20 +19,23 @@ namespace Textamina.Markdig.Syntax
{
public override MatchLineResult Match(BlockParserState state)
{
var liner = state.Line;
liner.SkipLeadingSpaces3();
if (state.IsCodeIndent)
{
return MatchLineResult.None;
}
var column = liner.Start;
var column = state.Column;
// 4.1 Thematic breaks
// A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching -, _, or * characters, each followed optionally by any number of spaces
var c = liner.Current;
var c = state.CurrentChar;
int count = 0;
var matchChar = (char)0;
bool hasSpacesSinceLastMatch = false;
bool hasInnerSpaces = false;
while (!liner.IsEol)
int offset = 0;
while (c != '\0')
{
if (count == 0 && (c == '-' || c == '_' || c == '*'))
{
@@ -56,7 +59,9 @@ namespace Textamina.Markdig.Syntax
{
hasSpacesSinceLastMatch = true;
}
c = liner.NextChar();
offset++;
c = state.PeekChar(offset);
}
// If it as less than 3 chars or it is a setex heading and we are already in a paragraph, let the paragraph handle it
@@ -77,8 +82,8 @@ namespace Textamina.Markdig.Syntax
return MatchLineResult.None;
}
state.NewBlocks.Push(new BreakBlock(this) { Column = column });
return MatchLineResult.Last;
state.NewBlocks.Push(new ThematicBreakBlock(this) { Column = column });
return MatchLineResult.LastDiscard;
}
}
}

View File

@@ -63,7 +63,6 @@
<Compile Include="Syntax\Inlines\LinkDelimiterInline.cs" />
<Compile Include="Syntax\Inlines\LiteralInline.cs" />
<Compile Include="Syntax\Inlines\RawHtmlInline.cs" />
<Compile Include="Syntax\StringSlice.cs" />
<Compile Include="Syntax\Block.cs" />
<Compile Include="Syntax\ContainerBlock.cs" />
<Compile Include="Syntax\HtmlBlock.cs" />
@@ -75,7 +74,8 @@
<Compile Include="Syntax\ListBlock.cs" />
<Compile Include="Syntax\ListItemBlock.cs" />
<Compile Include="Syntax\QuoteBlock.cs" />
<Compile Include="Syntax\BreakBlock.cs" />
<Compile Include="Syntax\StringSliceListExtensions.cs" />
<Compile Include="Syntax\ThematicBreakBlock.cs" />
<Compile Include="Helpers\CharHelper.cs" />
<Compile Include="Syntax\CodeBlock.cs" />
<Compile Include="Syntax\Document.cs" />
@@ -86,8 +86,8 @@
<Compile Include="Syntax\Inlines\Inline.cs" />
<Compile Include="Syntax\ParagraphBlock.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Syntax\StringLine.cs" />
<Compile Include="Syntax\StringLineGroup.cs" />
<Compile Include="Syntax\StringSlice.cs" />
<Compile Include="Syntax\StringSliceList.cs" />
</ItemGroup>
<ItemGroup>
<None Include="project.json" />