-
-
Save PolarbearDK/dbfc1fd8d0ffd7101651 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Irony.Parsing; | |
namespace MarkupExtensionParser | |
{ | |
// Grammar for Xaml markup extension: See https://msdn.microsoft.com/en-us/library/ee200269.aspx | |
// MarkupExtension = "{" TYPENAME 0*1Arguments "}" | |
// Arguments = (NamedArgs / (PositionalArgs 0*1("," NamedArgs)) | |
// NamedArgs = NamedArg*("," NamedArg) | |
// NamedArg = MEMBERNAME "=" STRING | |
// PositionalArgs = NamedArg / (STRING 0*1( "," PositionalArgs)) | |
[Language("XamlMarkupExtension", "1.0", "Xaml Markup Extension")] | |
public class XamlMarkupExtensionGrammar : Grammar | |
{ | |
public XamlMarkupExtensionGrammar() | |
{ | |
// Non Terminals | |
var markupExtension = new NonTerminal("MarkupExtension"); | |
var arguments = new NonTerminal("Arguments"); | |
var namedArgs = new NonTerminal("NamedArgs"); | |
var namedArg = new NonTerminal("NamedArg"); | |
var positionalArgs = new NonTerminal("PositionalArgs"); | |
var argument = new NonTerminal("Argument"); | |
// Terminals | |
var typeName = new TypeNameTerminal("TYPENAME"); | |
var memberName = new MemberNameOrStringTerminal("MEMBERNAME", true); | |
var @string = new MemberNameOrStringTerminal("STRING", false); | |
// Setup rules | |
markupExtension.Rule = "{" + typeName + "}" | |
| "{" + typeName + arguments + "}"; | |
arguments.Rule = namedArgs | |
| positionalArgs | |
| positionalArgs + "," + namedArgs; | |
namedArgs.Rule = namedArg | |
| namedArg + "," + namedArgs; | |
namedArg.Rule = memberName + "=" + argument; | |
positionalArgs.Rule = namedArgs | |
| argument | |
| argument + "," + positionalArgs; | |
argument.Rule = markupExtension | |
| @string; | |
this.Root = markupExtension; | |
base.MarkTransient(arguments, argument, namedArgs, positionalArgs); | |
} | |
} | |
internal class TypeNameTerminal : Terminal | |
{ | |
public TypeNameTerminal(string name) : base(name) | |
{ | |
} | |
public override Token TryMatch(ParsingContext context, ISourceStream source) | |
{ | |
while (!source.EOF()) | |
{ | |
switch (source.PreviewChar) | |
{ | |
case '\n': | |
case '\r': | |
case ' ': | |
case '}': | |
if (source.PreviewPosition > source.Position) | |
return source.CreateToken(this.OutputTerminal); | |
return context.CreateErrorToken(Name + " was expected"); | |
} | |
source.PreviewPosition++; | |
} | |
return context.CreateErrorToken("Unbalanced " + Name); | |
} | |
} | |
internal class MemberNameOrStringTerminal : Terminal | |
{ | |
private readonly bool _allowTrailingEqual; | |
public MemberNameOrStringTerminal(string name, bool allowTrailingEqual) : base(name) | |
{ | |
_allowTrailingEqual = allowTrailingEqual; | |
} | |
public override Token TryMatch(ParsingContext context, ISourceStream source) | |
{ | |
return MatchQuoted(context, source) ?? MatchUnquoted(context, source); | |
} | |
private Token MatchQuoted(ParsingContext context, ISourceStream source) | |
{ | |
char quoteChar = source.PreviewChar; | |
if (quoteChar != '\'' && quoteChar != '"') | |
return null; | |
source.PreviewPosition++; | |
while (!source.EOF()) | |
{ | |
if (source.PreviewChar == quoteChar) | |
{ | |
source.PreviewPosition++; | |
return source.CreateToken(this.OutputTerminal); | |
} | |
// Escaped? | |
if (source.PreviewChar == '\\') | |
{ | |
// Consume next | |
++source.PreviewPosition; | |
} | |
source.PreviewPosition++; | |
} | |
return context.CreateErrorToken("Unbalanced quoted string"); | |
} | |
private Token MatchUnquoted(ParsingContext context, ISourceStream source) | |
{ | |
var runningBraceTotal = 0; | |
while (!source.EOF()) | |
{ | |
switch (source.PreviewChar) | |
{ | |
case '{': | |
runningBraceTotal++; | |
break; | |
case '}': | |
if (--runningBraceTotal < 0) | |
return CreateToken(source); | |
break; | |
case ',': | |
if (runningBraceTotal == 0) | |
return CreateToken(source); | |
break; | |
case '=': | |
if (runningBraceTotal == 0) | |
{ | |
return _allowTrailingEqual | |
? CreateToken(source) | |
: null; | |
} | |
break; | |
case '\\': | |
source.PreviewPosition++; | |
break; | |
} | |
source.PreviewPosition++; | |
} | |
return context.CreateErrorToken("Unterminated string terminal"); | |
} | |
protected Token CreateToken(ISourceStream source) | |
{ | |
if (source.PreviewPosition > source.Position) | |
return source.CreateToken(this.OutputTerminal); | |
return null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment