Skip to content

Instantly share code, notes, and snippets.

@garuma
Last active January 13, 2016 19:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save garuma/6152148 to your computer and use it in GitHub Desktop.
Save garuma/6152148 to your computer and use it in GitHub Desktop.
%{
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
namespace MarkdownParser
{
class Counter
{
public Counter (int count = 0)
{
Count = count;
}
public int Count {
get;
set;
}
}
public class Parser
{
int yacc_verbose_flag = 0;
public IEnumerable<IBlock> Parse (string input)
{
return Parse (new StringReader (input));
}
public IEnumerable<IBlock> Parse (TextReader reader)
{
var lexer = new Tokenizer (reader);
return (IEnumerable<IBlock>)this.yyparse (lexer);
}
%}
%token ERROR
%token TEXT /* A string of character until the next newline/span */
%token EMPTY_LINE
%token NEWLINE
%token TRIPLE_QUOTE
%token HASH
%token HEADING_LINE
%token HEADING_LINE_DASH
%token EMPH_MARKER
%token STRONG_MARKER
%token BULLET_MARKER
%token INLINE_CODE_MARKER
%token URL_MARKER
%start document
%%
document: opt_block_list { $$ = $1; ((List<IBlock>)$$).Reverse (); }
opt_block_list
: /* empty */ { $$ = new List<IBlock> (); }
| block opt_block_list { ((List<IBlock>)$2).Add ((IBlock)$1); $$ = $2; }
block
: block_inner EMPTY_LINE { $$ = $1; }
block_inner
: paragraph { $$ = $1; }
| heading { $$ = $1; }
| code { $$ = $1; }
| bullet_list { $$ = $1; }
paragraph: span_list NEWLINE { $$ = new Paragraph ((IList<ISpan>)$1); }
heading
: atx_heading { $$ = $1; }
/* | setex_heading { $$ = $1; }*/
code
: TRIPLE_QUOTE TEXT NEWLINE opt_text_list TRIPLE_QUOTE NEWLINE { $$ = new Code (language: ((string)$2).Trim (), content: string.Join (Environment.NewLine, (IEnumerable<string>)$4)); }
opt_text_list: opt_text_list_inner { $$ = $1; ((List<string>)$1).Reverse (); }
opt_text_list_inner
: /* empty */ { $$ = new List<string> (); }
| text opt_text_list_inner { ((List<string>)$2).Add ((string)$1); $$ = $2; }
bullet_list
: BULLET_MARKER
span_list: span_list_inner { $$ = $1; ((List<ISpan>)$$).Reverse (); }
span_list_inner
: span { $$ = new List<ISpan> { (ISpan)$1 }; }
| span span_list_inner { ((List<ISpan>)$2).Add ((ISpan)$1); $$ = $2; }
span
: TEXT { $$ = new FormatedSpan (SpanFormating.None, (string)$1); }
| url { $$ = $1; }
| emphasis { $$ = $1; }
| strong { $$ = $1; }
| inline_code { $$ = $1; }
url: URL_MARKER TEXT URL_MARKER URL_MARKER TEXT URL_MARKER { $$ = new UrlSpan (href: (string)$5, content: (string)$2); }
inline_code: INLINE_CODE_MARKER TEXT INLINE_CODE_MARKER { $$ = new FormatedSpan (SpanFormating.Code, (string)$1); }
emphasis: EMPH_MARKER TEXT EMPH_MARKER { $$ = new FormatedSpan (SpanFormating.Emphasis, (string)$2); }
strong: STRONG_MARKER TEXT STRONG_MARKER { $$ = new FormatedSpan (SpanFormating.Strong, (string)$2); }
text: TEXT NEWLINE { $$ = $1; }
atx_heading
: hash_list text { $$ = new Heading (HeadingStyle.Atx, depth: ((Counter)$1).Count, content: ((string)$2).Trim ()); }
hash_list
: HASH { $$ = new Counter (1); }
| HASH hash_list { ((Counter)$2).Count++; $$ = $2; }
/*setex_heading: TEXT NEWLINE setex_heading_line NEWLINE { ((Heading)$3).Content = (string)$1; $$ = $3; }
setex_heading_line
: HEADING_LINE { $$ = new Heading (HeadingStyle.Setex, depth: 0); }
| HEADING_LINE_DASH { $$ = new Heading (HeadingStyle.Setex, depth: 1); }*/
%%
}
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using NUnit.Framework;
using MarkdownParser;
namespace MarkdownParserTests
{
[TestFixture]
public class Test
{
[Test]
public void TestMarkdownAstTest ()
{
var ast = new IBlock[] {
new Heading (HeadingStyle.Atx, 1, "My Test Markdown file"),
new Heading (HeadingStyle.Atx, 2, "Introduction"),
new Paragraph (new ISpan[] { new FormatedSpan ("This is a test Markdown file") }),
new Paragraph (new ISpan[] {
new FormatedSpan ("It has some "),
new FormatedSpan (SpanFormating.Emphasis, "emphasis"),
new FormatedSpan (" and some "),
new FormatedSpan (SpanFormating.Strong, "strong"),
new FormatedSpan (" stuff to talk about"),
}),
new Code (language: "csharp", content: @"SomeCodeIsEvenDisplayed ()
{
public void IntoSomething ()
{
Console.WriteLine (""That looks like code"");
}
}"),
new Paragraph (new ISpan[] {
new FormatedSpan ("You can even embed cool url like "),
new UrlSpan (href: "http://neteril.org", content: "my website"),
new FormatedSpan (".")
})
};
AssertAst (ast, "MarkdownParserTests.MarkdownSnippets.test_markdown.md");
}
void AssertAst (IEnumerable<IBlock> expectedAst, string markdownRes)
{
var reader = new StreamReader (GetType ().Assembly.GetManifestResourceStream (markdownRes));
var parser = new Parser ();
IEnumerable<IBlock> ast = null;
try {
ast = parser.Parse (reader);
} catch (Exception e) {
Assert.Fail ("Parser failed: {0}", e.ToString ());
}
CollectionAssert.AreEqual (expectedAst, ast);
}
}
}

My Test Markdown file

Introduction

This is a test Markdown file

It has some emphasis and some strong stuff to talk about

SomeCodeIsEvenDisplayed ()
{
	public void IntoSomething ()
	{
		Console.WriteLine ("That looks like code");
	}
}

You can even embed cool url like my website.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment