Browse Source

Convert current binding parsing to create an AST.

pull/3001/head
Jeremy Koritzinsky 7 years ago
parent
commit
30066c5e70
  1. 6
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  2. 386
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  3. 341
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

6
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -3,6 +3,12 @@
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Remove="Markup\Parsers\Nodes\ExpressionGrammer" />
</ItemGroup>
<ItemGroup>
<None Include="Markup\Parsers\BindingExpressionGrammar.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />

386
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs

@ -0,0 +1,386 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Data.Core;
using Avalonia.Markup.Parsers.Nodes;
using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Markup.Parsers
{
internal enum SourceMode
{
Data,
Control
}
internal static class BindingExpressionGrammar
{
public static (IList<INode> Nodes, SourceMode Mode) Parse(ref CharacterReader r)
{
var nodes = new List<INode>();
var state = State.Start;
var mode = SourceMode.Data;
while (!r.End && state != State.End)
{
switch (state)
{
case State.Start:
state = ParseStart(ref r, nodes);
break;
case State.AfterMember:
state = ParseAfterMember(ref r, nodes);
break;
case State.BeforeMember:
state = ParseBeforeMember(ref r, nodes);
break;
case State.AttachedProperty:
state = ParseAttachedProperty(ref r, nodes);
break;
case State.Indexer:
state = ParseIndexer(ref r, nodes);
break;
case State.ElementName:
state = ParseElementName(ref r, nodes);
mode = SourceMode.Control;
break;
case State.RelativeSource:
state = ParseRelativeSource(ref r, nodes);
mode = SourceMode.Control;
break;
}
}
if (state == State.BeforeMember)
{
throw new ExpressionParseException(r.Position, "Unexpected end of expression.");
}
return (nodes, mode);
}
private static State ParseStart(ref CharacterReader r, IList<INode> nodes)
{
if (ParseNot(ref r))
{
nodes.Add(new NotNode());
return State.Start;
}
else if (ParseSharp(ref r))
{
return State.ElementName;
}
else if (ParseDollarSign(ref r))
{
return State.RelativeSource;
}
else if (ParseOpenBrace(ref r))
{
return State.AttachedProperty;
}
else if (PeekOpenBracket(ref r))
{
return State.Indexer;
}
else if (ParseDot(ref r))
{
nodes.Add(new EmptyExpressionNode());
return State.End;
}
else
{
var identifier = r.ParseIdentifier();
if (!identifier.IsEmpty)
{
nodes.Add(new PropertyNameNode { PropertyName = identifier.ToString() });
return State.AfterMember;
}
}
return State.End;
}
private static State ParseAfterMember(ref CharacterReader r, IList<INode> nodes)
{
if (ParseMemberAccessor(ref r))
{
return State.BeforeMember;
}
else if (ParseStreamOperator(ref r))
{
nodes.Add(new StreamNode());
return State.AfterMember;
}
else if (PeekOpenBracket(ref r))
{
return State.Indexer;
}
return State.End;
}
private static State ParseBeforeMember(ref CharacterReader r, IList<INode> nodes)
{
if (ParseOpenBrace(ref r))
{
return State.AttachedProperty;
}
else
{
var identifier = r.ParseIdentifier();
if (!identifier.IsEmpty)
{
nodes.Add(new PropertyNameNode { PropertyName = identifier.ToString() });
return State.AfterMember;
}
return State.End;
}
}
private static State ParseAttachedProperty(ref CharacterReader r, List<INode> nodes)
{
var (ns, owner) = ParseTypeName(ref r);
if (r.End || !r.TakeIf('.'))
{
throw new ExpressionParseException(r.Position, "Invalid attached property name.");
}
var name = r.ParseIdentifier();
if (r.End || !r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, "Expected ')'.");
}
nodes.Add(new AttachedPropertyNameNode
{
Namespace = ns.ToString(),
TypeName = owner.ToString(),
PropertyName = name.ToString()
});
return State.AfterMember;
}
private static State ParseIndexer(ref CharacterReader r, List<INode> nodes)
{
var args = r.ParseArguments('[', ']');
if (args.Count == 0)
{
throw new ExpressionParseException(r.Position, "Indexer may not be empty.");
}
nodes.Add(new IndexerNode { Arguments = args });
return State.AfterMember;
}
private static State ParseElementName(ref CharacterReader r, List<INode> nodes)
{
var name = r.ParseIdentifier();
if (name == null)
{
throw new ExpressionParseException(r.Position, "Element name expected after '#'.");
}
nodes.Add(new NameNode { Name = name.ToString() });
return State.AfterMember;
}
private static State ParseRelativeSource(ref CharacterReader r, List<INode> nodes)
{
var mode = r.ParseIdentifier();
if (mode.Equals("self".AsSpan(), StringComparison.InvariantCulture))
{
nodes.Add(new SelfNode());
}
else if (mode.Equals("parent".AsSpan(), StringComparison.InvariantCulture))
{
string ancestorNamespace = null;
string ancestorType = null;
var ancestorLevel = 0;
if (PeekOpenBracket(ref r))
{
var args = r.ParseArguments('[', ']', ';');
if (args.Count > 2 || args.Count == 0)
{
throw new ExpressionParseException(r.Position, "Too many arguments in RelativeSource syntax sugar");
}
else if (args.Count == 1)
{
if (int.TryParse(args[0], out int level))
{
ancestorType = null;
ancestorLevel = level;
}
else
{
var reader = new CharacterReader(args[0].AsSpan());
(ancestorNamespace, ancestorType) = ParseTypeName(ref reader);
}
}
else
{
var reader = new CharacterReader(args[0].AsSpan());
(ancestorNamespace, ancestorType) = ParseTypeName(ref reader);
ancestorLevel = int.Parse(args[1]);
}
}
nodes.Add(new AncestorNode
{
Namespace = ancestorNamespace,
TypeName = ancestorType,
Level = ancestorLevel
});
}
else
{
throw new ExpressionParseException(r.Position, "Unknown RelativeSource mode.");
}
return State.AfterMember;
}
private static TypeName ParseTypeName(ref CharacterReader r)
{
ReadOnlySpan<char> ns, typeName;
ns = ReadOnlySpan<char>.Empty;
var typeNameOrNamespace = r.ParseIdentifier();
if (!r.End && r.TakeIf(':'))
{
ns = typeNameOrNamespace;
typeName = r.ParseIdentifier();
}
else
{
typeName = typeNameOrNamespace;
}
return new TypeName(ns, typeName);
}
private static bool ParseNot(ref CharacterReader r)
{
return !r.End && r.TakeIf('!');
}
private static bool ParseMemberAccessor(ref CharacterReader r)
{
return !r.End && r.TakeIf('.');
}
private static bool ParseOpenBrace(ref CharacterReader r)
{
return !r.End && r.TakeIf('(');
}
private static bool PeekOpenBracket(ref CharacterReader r)
{
return !r.End && r.PeekOneOrThrow == '[';
}
private static bool ParseStreamOperator(ref CharacterReader r)
{
return !r.End && r.TakeIf('^');
}
private static bool ParseDollarSign(ref CharacterReader r)
{
return !r.End && r.TakeIf('$');
}
private static bool ParseSharp(ref CharacterReader r)
{
return !r.End && r.TakeIf('#');
}
private static bool ParseDot(ref CharacterReader r)
{
return !r.End && r.TakeIf('.');
}
private enum State
{
Start,
RelativeSource,
ElementName,
AfterMember,
BeforeMember,
AttachedProperty,
Indexer,
End,
}
private readonly ref struct TypeName
{
public TypeName(ReadOnlySpan<char> ns, ReadOnlySpan<char> typeName)
{
Namespace = ns;
Type = typeName;
}
public readonly ReadOnlySpan<char> Namespace;
public readonly ReadOnlySpan<char> Type;
public void Deconstruct(out string ns, out string typeName)
{
ns = Namespace.ToString();
typeName = Type.ToString();
}
}
public interface INode {}
public class EmptyExpressionNode : INode { }
public class PropertyNameNode : INode
{
public string PropertyName { get; set; }
}
public class AttachedPropertyNameNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
public string PropertyName { get; set; }
}
public class IndexerNode : INode
{
public IList<string> Arguments { get; set; }
}
public class NotNode : INode {}
public class StreamNode : INode {}
public class SelfNode : INode {}
public class NameNode : INode
{
public string Name { get; set; }
}
public class AncestorNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
public int Level { get; set; }
}
}
}

341
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@ -10,12 +10,6 @@ using System.Linq;
namespace Avalonia.Markup.Parsers
{
internal enum SourceMode
{
Data,
Control
}
internal class ExpressionParser
{
private readonly bool _enableValidation;
@ -29,332 +23,83 @@ namespace Avalonia.Markup.Parsers
public (ExpressionNode Node, SourceMode Mode) Parse(ref CharacterReader r)
{
var nodes = new List<ExpressionNode>();
var state = State.Start;
var mode = SourceMode.Data;
ExpressionNode node = null;
var (astNodes, mode) = BindingExpressionGrammar.Parse(ref r);
while (!r.End && state != State.End)
foreach (var astNode in astNodes)
{
switch (state)
ExpressionNode nextNode = null;
switch (astNode)
{
case State.Start:
state = ParseStart(ref r, nodes);
case BindingExpressionGrammar.EmptyExpressionNode _:
nextNode = new EmptyExpressionNode();
break;
case State.AfterMember:
state = ParseAfterMember(ref r, nodes);
case BindingExpressionGrammar.NotNode _:
nextNode = new LogicalNotNode();
break;
case State.BeforeMember:
state = ParseBeforeMember(ref r, nodes);
case BindingExpressionGrammar.StreamNode _:
nextNode = new StreamNode();
break;
case State.AttachedProperty:
state = ParseAttachedProperty(ref r, nodes);
case BindingExpressionGrammar.PropertyNameNode propName:
nextNode = new PropertyAccessorNode(propName.PropertyName, _enableValidation);
break;
case State.Indexer:
state = ParseIndexer(ref r, nodes);
case BindingExpressionGrammar.IndexerNode indexer:
nextNode = new StringIndexerNode(indexer.Arguments);
break;
case State.ElementName:
state = ParseElementName(ref r, nodes);
mode = SourceMode.Control;
case BindingExpressionGrammar.AttachedPropertyNameNode attachedProp:
nextNode = ParseAttachedProperty(attachedProp);
break;
case State.RelativeSource:
state = ParseRelativeSource(ref r, nodes);
mode = SourceMode.Control;
case BindingExpressionGrammar.SelfNode _:
nextNode = new SelfNode();
break;
case BindingExpressionGrammar.AncestorNode ancestor:
nextNode = ParseFindAncestor(ancestor);
break;
case BindingExpressionGrammar.NameNode elementName:
nextNode = new ElementNameNode(elementName.Name);
break;
}
}
if (state == State.BeforeMember)
{
throw new ExpressionParseException(r.Position, "Unexpected end of expression.");
}
for (int n = 0; n < nodes.Count - 1; ++n)
{
nodes[n].Next = nodes[n + 1];
}
return (nodes.FirstOrDefault(), mode);
}
private State ParseStart(ref CharacterReader r, IList<ExpressionNode> nodes)
{
if (ParseNot(ref r))
{
nodes.Add(new LogicalNotNode());
return State.Start;
}
else if (ParseSharp(ref r))
{
return State.ElementName;
}
else if (ParseDollarSign(ref r))
{
return State.RelativeSource;
}
else if (ParseOpenBrace(ref r))
{
return State.AttachedProperty;
}
else if (PeekOpenBracket(ref r))
{
return State.Indexer;
}
else if (ParseDot(ref r))
{
nodes.Add(new EmptyExpressionNode());
return State.End;
}
else
{
var identifier = r.ParseIdentifier();
if (!identifier.IsEmpty)
if (node is null)
{
nodes.Add(new PropertyAccessorNode(identifier.ToString(), _enableValidation));
return State.AfterMember;
node = nextNode;
}
}
return State.End;
}
private static State ParseAfterMember(ref CharacterReader r, IList<ExpressionNode> nodes)
{
if (ParseMemberAccessor(ref r))
{
return State.BeforeMember;
}
else if (ParseStreamOperator(ref r))
{
nodes.Add(new StreamNode());
return State.AfterMember;
}
else if (PeekOpenBracket(ref r))
{
return State.Indexer;
}
return State.End;
}
private State ParseBeforeMember(ref CharacterReader r, IList<ExpressionNode> nodes)
{
if (ParseOpenBrace(ref r))
{
return State.AttachedProperty;
}
else
{
var identifier = r.ParseIdentifier();
if (!identifier.IsEmpty)
else
{
nodes.Add(new PropertyAccessorNode(identifier.ToString(), _enableValidation));
return State.AfterMember;
node.Next = nextNode;
}
return State.End;
}
}
private State ParseAttachedProperty(ref CharacterReader r, List<ExpressionNode> nodes)
{
var (ns, owner) = ParseTypeName(ref r);
if (r.End || !r.TakeIf('.'))
{
throw new ExpressionParseException(r.Position, "Invalid attached property name.");
}
var name = r.ParseIdentifier();
if (r.End || !r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, "Expected ')'.");
}
if (_typeResolver == null)
{
throw new InvalidOperationException("Cannot parse a binding path with an attached property without a type resolver. Maybe you can use a LINQ Expression binding path instead?");
}
var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(ns.ToString(), owner.ToString()), name.ToString());
nodes.Add(new AvaloniaPropertyAccessorNode(property, _enableValidation));
return State.AfterMember;
}
private State ParseIndexer(ref CharacterReader r, List<ExpressionNode> nodes)
{
var args = r.ParseArguments('[', ']');
if (args.Count == 0)
{
throw new ExpressionParseException(r.Position, "Indexer may not be empty.");
}
nodes.Add(new StringIndexerNode(args));
return State.AfterMember;
return (node, mode);
}
private State ParseElementName(ref CharacterReader r, List<ExpressionNode> nodes)
private FindAncestorNode ParseFindAncestor(BindingExpressionGrammar.AncestorNode node)
{
var name = r.ParseIdentifier();
Type ancestorType = null;
var ancestorLevel = node.Level;
if (name == null)
if (!(node.Namespace is null) && !(node.TypeName is null))
{
throw new ExpressionParseException(r.Position, "Element name expected after '#'.");
}
nodes.Add(new ElementNameNode(name.ToString()));
return State.AfterMember;
}
private State ParseRelativeSource(ref CharacterReader r, List<ExpressionNode> nodes)
{
var mode = r.ParseIdentifier();
if (mode.Equals("self".AsSpan(), StringComparison.InvariantCulture))
{
nodes.Add(new SelfNode());
}
else if (mode.Equals("parent".AsSpan(), StringComparison.InvariantCulture))
{
Type ancestorType = null;
var ancestorLevel = 0;
if (PeekOpenBracket(ref r))
if (_typeResolver == null)
{
var args = r.ParseArguments('[', ']', ';');
if (args.Count > 2 || args.Count == 0)
{
throw new ExpressionParseException(r.Position, "Too many arguments in RelativeSource syntax sugar");
}
else if (args.Count == 1)
{
if (int.TryParse(args[0], out int level))
{
ancestorType = null;
ancestorLevel = level;
}
else
{
var reader = new CharacterReader(args[0].AsSpan());
var typeName = ParseTypeName(ref reader);
ancestorType = _typeResolver(typeName.Namespace.ToString(), typeName.Type.ToString());
}
}
else
{
var reader = new CharacterReader(args[0].AsSpan());
var typeName = ParseTypeName(ref reader);
ancestorType = _typeResolver(typeName.Namespace.ToString(), typeName.Type.ToString());
ancestorLevel = int.Parse(args[1]);
}
throw new InvalidOperationException("Cannot parse a binding path with a typed FindAncestor without a type resolver. Maybe you can use a LINQ Expression binding path instead?");
}
nodes.Add(new FindAncestorNode(ancestorType, ancestorLevel));
}
else
{
throw new ExpressionParseException(r.Position, "Unknown RelativeSource mode.");
}
return State.AfterMember;
}
private static TypeName ParseTypeName(ref CharacterReader r)
{
ReadOnlySpan<char> ns, typeName;
ns = ReadOnlySpan<char>.Empty;
var typeNameOrNamespace = r.ParseIdentifier();
if (!r.End && r.TakeIf(':'))
{
ns = typeNameOrNamespace;
typeName = r.ParseIdentifier();
ancestorType = _typeResolver(node.Namespace, node.TypeName);
}
else
{
typeName = typeNameOrNamespace;
}
return new TypeName(ns, typeName);
}
private static bool ParseNot(ref CharacterReader r)
{
return !r.End && r.TakeIf('!');
}
private static bool ParseMemberAccessor(ref CharacterReader r)
{
return !r.End && r.TakeIf('.');
}
private static bool ParseOpenBrace(ref CharacterReader r)
{
return !r.End && r.TakeIf('(');
}
private static bool PeekOpenBracket(ref CharacterReader r)
{
return !r.End && r.PeekOneOrThrow == '[';
}
private static bool ParseStreamOperator(ref CharacterReader r)
{
return !r.End && r.TakeIf('^');
}
private static bool ParseDollarSign(ref CharacterReader r)
{
return !r.End && r.TakeIf('$');
}
private static bool ParseSharp(ref CharacterReader r)
{
return !r.End && r.TakeIf('#');
}
private static bool ParseDot(ref CharacterReader r)
{
return !r.End && r.TakeIf('.');
}
private enum State
{
Start,
RelativeSource,
ElementName,
AfterMember,
BeforeMember,
AttachedProperty,
Indexer,
End,
return new FindAncestorNode(ancestorType, ancestorLevel);
}
private readonly ref struct TypeName
private AvaloniaPropertyAccessorNode ParseAttachedProperty(BindingExpressionGrammar.AttachedPropertyNameNode node)
{
public TypeName(ReadOnlySpan<char> ns, ReadOnlySpan<char> typeName)
if (_typeResolver == null)
{
Namespace = ns;
Type = typeName;
throw new InvalidOperationException("Cannot parse a binding path with an attached property without a type resolver. Maybe you can use a LINQ Expression binding path instead?");
}
public readonly ReadOnlySpan<char> Namespace;
public readonly ReadOnlySpan<char> Type;
var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(node.Namespace, node.TypeName), node.PropertyName);
public void Deconstruct(out ReadOnlySpan<char> ns, out ReadOnlySpan<char> typeName)
{
ns = Namespace;
typeName = Type;
}
return new AvaloniaPropertyAccessorNode(property, _enableValidation);
}
}
}

Loading…
Cancel
Save