3 changed files with 435 additions and 298 deletions
@ -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; } |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue