diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs index 563b372c78..05b3b29be0 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs +++ b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs @@ -51,10 +51,10 @@ namespace Avalonia.Markup.Data.Parsers } } - throw new ExpressionParseException(r.Position, "Expected ']'."); + throw new ExpressionParseException(r.Position, $"Expected '{close}'."); } - return null; + throw new ExpressionParseException(r.Position, $"Expected '{open}'."); } } } diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs b/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs index 93ddf77376..f31f6eccb7 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs +++ b/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs @@ -40,6 +40,10 @@ namespace Avalonia.Markup.Data.Parsers case State.AttachedProperty: state = ParseAttachedProperty(r, nodes); break; + + case State.Indexer: + state = ParseIndexer(r, nodes); + break; } } @@ -67,6 +71,10 @@ namespace Avalonia.Markup.Data.Parsers { return State.AttachedProperty; } + else if (PeekOpenBracket(r)) + { + return State.Indexer; + } else { var identifier = IdentifierParser.Parse(r); @@ -92,20 +100,9 @@ namespace Avalonia.Markup.Data.Parsers nodes.Add(new StreamNode()); return State.AfterMember; } - else + else if (PeekOpenBracket(r)) { - var args = ArgumentListParser.Parse(r, '[', ']'); - - if (args != null) - { - if (args.Count == 0) - { - throw new ExpressionParseException(r.Position, "Indexer may not be empty."); - } - - nodes.Add(new IndexerNode(args)); - return State.AfterMember; - } + return State.Indexer; } return State.End; @@ -151,6 +148,19 @@ namespace Avalonia.Markup.Data.Parsers return State.AfterMember; } + private State ParseIndexer(Reader r, List nodes) + { + var args = ArgumentListParser.Parse(r, '[', ']'); + + if (args.Count == 0) + { + throw new ExpressionParseException(r.Position, "Indexer may not be empty."); + } + + nodes.Add(new IndexerNode(args)); + return State.AfterMember; + } + private static bool ParseNot(Reader r) { return !r.End && r.TakeIf('!'); @@ -166,6 +176,11 @@ namespace Avalonia.Markup.Data.Parsers return !r.End && r.TakeIf('('); } + private static bool PeekOpenBracket(Reader r) + { + return !r.End && r.Peek == '['; + } + private static bool ParseStreamOperator(Reader r) { return !r.End && r.TakeIf('^'); @@ -177,6 +192,7 @@ namespace Avalonia.Markup.Data.Parsers AfterMember, BeforeMember, AttachedProperty, + Indexer, End, } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs index a68213baee..1bdbf0d895 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs @@ -338,6 +338,18 @@ namespace Avalonia.Markup.UnitTests.Data GC.KeepAlive(data); } + [Fact] + public async Task Indexer_Only_Binding_Works() + { + var data = new[] { 1, 2, 3 }; + + var target = new ExpressionObserver(data, "[1]"); + + var value = await target.Take(1); + + Assert.Equal(data[1], value); + } + private class NonIntegerIndexer : NotifyingBase { private readonly Dictionary _storage = new Dictionary();