Skip to content

Instantly share code, notes, and snippets.

@PolarbearDK
Last active August 29, 2015 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PolarbearDK/dbfc1fd8d0ffd7101651 to your computer and use it in GitHub Desktop.
Save PolarbearDK/dbfc1fd8d0ffd7101651 to your computer and use it in GitHub Desktop.
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