Browse Source

Create new Selector parser that does not have a dependency on Sprache.

Jeremy Koritzinsky 7 years ago
parent
commit
2f087c350c

+ 5 - 5
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@@ -81,7 +81,7 @@ namespace Avalonia.Markup.Parsers
             }
             else
             {
-                var identifier = IdentifierParser.Parse(r);
+                var identifier = r.ParseIdentifier();
 
                 if (identifier != null)
                 {
@@ -120,7 +120,7 @@ namespace Avalonia.Markup.Parsers
             }
             else
             {
-                var identifier = IdentifierParser.Parse(r);
+                var identifier = r.ParseIdentifier();
 
                 if (identifier != null)
                 {
@@ -136,12 +136,12 @@ namespace Avalonia.Markup.Parsers
         {
             string ns = string.Empty;
             string owner;
-            var ownerOrNamespace = IdentifierParser.Parse(r);
+            var ownerOrNamespace = r.ParseIdentifier();
 
             if (r.TakeIf(':'))
             {
                 ns = ownerOrNamespace;
-                owner = IdentifierParser.Parse(r);
+                owner = r.ParseIdentifier();
             }
             else
             {
@@ -153,7 +153,7 @@ namespace Avalonia.Markup.Parsers
                 throw new ExpressionParseException(r.Position, "Invalid attached property name.");
             }
 
-            var name = IdentifierParser.Parse(r);
+            var name = r.ParseIdentifier();
 
             if (r.End || !r.TakeIf(')'))
             {

+ 1 - 1
src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs

@@ -8,7 +8,7 @@ namespace Avalonia.Markup.Parsers
 {
     internal static class IdentifierParser
     {
-        public static string Parse(Reader r)
+        public static string ParseIdentifier(this Reader r)
         {
             if (IsValidIdentifierStart(r.Peek))
             {

+ 21 - 0
src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Text;
 
 namespace Avalonia.Markup.Parsers
 {
@@ -40,5 +41,25 @@ namespace Avalonia.Markup.Parsers
                 return false;
             }
         }
+
+        public bool TakeIf(Func<char, bool> condition)
+        {
+            if (condition(Peek))
+            {
+                Take();
+                return true;
+            }
+            return false;
+        }
+
+        public string TakeUntil(char c)
+        {
+            var builder = new StringBuilder();
+            while (!End && Peek != c)
+            {
+                builder.Append(Take());
+            }
+            return builder.ToString();
+        }
     }
 }

+ 270 - 107
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@@ -3,7 +3,7 @@
 
 using System.Collections.Generic;
 using System.Globalization;
-using Sprache;
+using Avalonia.Data.Core;
 
 // Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the 
 // only reason they have overridden Equals methods is for unit testing.
@@ -13,117 +13,280 @@ namespace Avalonia.Markup.Parsers
 {
     internal class SelectorGrammar
     {
-        public static readonly Parser<char> CombiningCharacter = Parse.Char(
-            c =>
-            {
-                var cat = CharUnicodeInfo.GetUnicodeCategory(c);
-                return cat == UnicodeCategory.NonSpacingMark ||
-                       cat == UnicodeCategory.SpacingCombiningMark;
-            },
-            "Connecting Character");
-
-        public static readonly Parser<char> ConnectingCharacter = Parse.Char(
-            c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.ConnectorPunctuation,
-            "Connecting Character");
-
-        public static readonly Parser<char> FormattingCharacter = Parse.Char(
-            c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.Format,
-            "Connecting Character");
-
-        public static readonly Parser<char> IdentifierStart = Parse.Letter.Or(Parse.Char('_'));
-
-        public static readonly Parser<char> IdentifierChar = Parse
-            .LetterOrDigit
-            .Or(ConnectingCharacter)
-            .Or(CombiningCharacter)
-            .Or(FormattingCharacter);
-
-        public static readonly Parser<string> Identifier =
-            from start in IdentifierStart.Once().Text()
-            from @char in IdentifierChar.Many().Text()
-            select start + @char;
-
-        public static readonly Parser<string> Namespace =
-            from ns in Parse.Letter.Many().Text()
-            from bar in Parse.Char('|')
-            select ns;
-
-        public static readonly Parser<OfTypeSyntax> OfType =
-            from ns in Namespace.Optional()
-            from identifier in Identifier
-            select new OfTypeSyntax
-            {
-                TypeName = identifier,
-                Xmlns = ns.GetOrDefault(),
+        private enum State
+        {
+            Start,
+            Middle,
+            Colon,
+            Class,
+            Name,
+            CanHaveType,
+            Traversal,
+            TypeName,
+            Property,
+            Template,
+            End,
+        }
+
+        public static IEnumerable<ISyntax> Parse(string s)
+        {
+            var r = new Reader(s);
+            var state = State.Start;
+            var selector = new List<ISyntax>();
+            while (!r.End && state != State.End)
+            {
+                ISyntax syntax = null;
+                switch (state)
+                {
+                    case State.Start:
+                        (state, syntax) = ParseStart(r);
+                        break;
+                    case State.Middle:
+                        (state, syntax) = ParseMiddle(r);
+                        break;
+                    case State.Colon:
+                        (state, syntax) = ParseColon(r);
+                        break;
+                    case State.Class:
+                        (state, syntax) = ParseClass(r);
+                        break;
+                    case State.Traversal:
+                        (state, syntax) = ParseTraversal(r);
+                        break;
+                    case State.TypeName:
+                        (state, syntax) = ParseTypeName(r);
+                        break;
+                    case State.CanHaveType:
+                        (state, syntax) = ParseCanHaveType(r);
+                        break;
+                    case State.Property:
+                        (state, syntax) = ParseProperty(r);
+                        break;
+                    case State.Template:
+                        (state, syntax) = ParseTemplate(r);
+                        break;
+                    case State.Name:
+                        (state, syntax) = ParseName(r);
+                        break;
+                }
+                if (syntax != null)
+                {
+                    selector.Add(syntax);
+                }
+            }
+
+            if (state != State.Start && state != State.Middle && state != State.End && state != State.CanHaveType)
+            {
+                throw new ExpressionParseException(r.Position, "Unexpected end of selector");
+            }
+
+            return selector;
+        }
+
+        private static (State, ISyntax) ParseStart(Reader r)
+        {
+            r.SkipWhitespace();
+            if (r.TakeIf(':'))
+            {
+                return (State.Colon, null);
+            }
+            else if (r.TakeIf('.'))
+            {
+                return (State.Class, null);
+            }
+            else if (r.TakeIf('#'))
+            {
+                return (State.Name, null);
+            }
+            return (State.TypeName, null);
+        }
+
+        private static (State, ISyntax) ParseMiddle(Reader r)
+        {
+            if (r.TakeIf(':'))
+            {
+                return (State.Colon, null);
+            }
+            else if (r.TakeIf('.'))
+            {
+                return (State.Class, null);
+            }
+            else if (r.TakeIf(char.IsWhiteSpace) || r.Peek == '>')
+            {
+                return (State.Traversal, null);
+            }
+            else if (r.TakeIf('/'))
+            {
+                return (State.Template, null);
+            }
+            else if (r.TakeIf('#'))
+            {
+                return (State.Name, null);
+            }
+            return (State.TypeName, null);
+        }
+
+        private static (State, ISyntax) ParseCanHaveType(Reader r)
+        {
+            if (r.TakeIf('['))
+            {
+                return (State.Property, null);
+            }
+            return (State.Middle, null);
+        }
+
+        private static (State, ISyntax) ParseColon(Reader r)
+        {
+            var identifier = r.ParseIdentifier();
+
+            if (string.IsNullOrEmpty(identifier))
+            {
+                throw new ExpressionParseException(r.Position, "Expected class name or is selector after ':'.");
+            }
+
+            if (identifier == "is" && r.TakeIf('('))
+            {
+                var syntax = ParseType<IsSyntax>(r);
+                if (r.End || !r.TakeIf(')'))
+                {
+                    throw new ExpressionParseException(r.Position, $"Expected ')', got {r.Peek}");
+                }
+
+                return (State.CanHaveType, syntax);
+            }
+            else
+            {
+                return (
+                    State.CanHaveType,
+                    new ClassSyntax
+                    {
+                        Class = ":" + identifier
+                    });
+            }
+        }
+
+        private static (State, ISyntax) ParseTraversal(Reader r)
+        {
+            r.SkipWhitespace();
+            if (r.TakeIf('>'))
+            {
+                r.SkipWhitespace();
+                return (State.Middle, new ChildSyntax());
+            }
+            else if (r.TakeIf('/'))
+            {
+                return (State.Template, null);
+            }
+            else if (!r.End)
+            {
+                return (State.Middle, new DescendantSyntax());
+            }
+            else
+            {
+                return (State.End, null);
+            }
+        }
+
+        private static (State, ISyntax) ParseClass(Reader r)
+        {
+            var @class = r.ParseIdentifier();
+            if (string.IsNullOrEmpty(@class))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected a class name after '.'.");
+            }
+
+            return (State.CanHaveType, new ClassSyntax { Class = @class });
+        }
+
+        private static (State, ISyntax) ParseTemplate(Reader r)
+        {
+            var template = r.ParseIdentifier();
+            if (template != nameof(template))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected 'template', got {template}");
+            }
+            else if (!r.TakeIf('/'))
+            {
+                throw new ExpressionParseException(r.Position, "Expected '/'");
+            }
+            return (State.Start, new TemplateSyntax());
+        }
+
+        private static (State, ISyntax) ParseName(Reader r)
+        {
+            var name = r.ParseIdentifier();
+            if (string.IsNullOrEmpty(name))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected a name after '#'.");
+            }
+            return (State.CanHaveType, new NameSyntax { Name = name });
+        }
+
+        private static (State, ISyntax) ParseTypeName(Reader r)
+        {
+            return (State.CanHaveType, ParseType<OfTypeSyntax>(r));
+        }
+
+        private static (State, ISyntax) ParseProperty(Reader r)
+        {
+            var property = r.ParseIdentifier();
+
+            if (!r.TakeIf('='))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected '=', got '{r.Peek}'");
+            }
+
+            var value = r.TakeUntil(']');
+
+            r.Take();
+
+            return (State.CanHaveType, new PropertySyntax { Property = property, Value = value });
+        }
+
+        private static TSyntax ParseType<TSyntax>(Reader r)
+            where TSyntax : ITypeSyntax, new()
+        {
+            string ns = null;
+            string type;
+            var namespaceOrTypeName = r.ParseIdentifier();
+
+            if (string.IsNullOrEmpty(namespaceOrTypeName))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected an identifier, got '{r.Peek}");
+            }
+
+            if (!r.End && r.TakeIf('|'))
+            {
+                ns = namespaceOrTypeName;
+                if (r.End)
+                {
+                    throw new ExpressionParseException(r.Position, $"Unexpected end of selector.");
+                }
+                type = r.ParseIdentifier();
+            }
+            else
+            {
+                type = namespaceOrTypeName;
+            }
+            return new TSyntax
+            {
+                Xmlns = ns,
+                TypeName = type
             };
+        }
 
-        public static readonly Parser<NameSyntax> Name =
-            from hash in Parse.Char('#')
-            from identifier in Identifier
-            select new NameSyntax { Name = identifier };
-
-        public static readonly Parser<char> ClassStart = Parse.Char('_').Or(Parse.Letter);
-
-        public static readonly Parser<char> ClassChar = ClassStart.Or(Parse.Numeric);
-
-        public static readonly Parser<string> ClassIdentifier =
-            from start in ClassStart.Once().Text()
-            from @char in ClassChar.Many().Text()
-            select start + @char;
-
-        public static readonly Parser<ClassSyntax> StandardClass =
-            from dot in Parse.Char('.').Once()
-            from identifier in ClassIdentifier
-            select new ClassSyntax { Class = identifier };
-
-        public static readonly Parser<ClassSyntax> Pseduoclass =
-            from colon in Parse.Char(':').Once()
-            from identifier in ClassIdentifier
-            select new ClassSyntax { Class = ':' + identifier };
-
-        public static readonly Parser<ClassSyntax> Class = StandardClass.Or(Pseduoclass);
-
-        public static readonly Parser<PropertySyntax> Property =
-            from open in Parse.Char('[').Once()
-            from identifier in Identifier
-            from eq in Parse.Char('=').Once()
-            from value in Parse.CharExcept(']').Many().Text()
-            from close in Parse.Char(']').Once()
-            select new PropertySyntax { Property = identifier, Value = value };
-
-        public static readonly Parser<ChildSyntax> Child = Parse.Char('>').Token().Return(new ChildSyntax());
-
-        public static readonly Parser<DescendantSyntax> Descendant =
-            from child in Parse.WhiteSpace.Many()
-            select new DescendantSyntax();
-
-        public static readonly Parser<TemplateSyntax> Template =
-            from template in Parse.String("/template/").Token()
-            select new TemplateSyntax();
-
-        public static readonly Parser<IsSyntax> Is =
-            from function in Parse.String(":is(")
-            from type in OfType
-            from close in Parse.Char(')')
-            select new IsSyntax { TypeName = type.TypeName, Xmlns = type.Xmlns };
-
-        public static readonly Parser<ISyntax> SingleSelector =
-            OfType
-            .Or<ISyntax>(Is)
-            .Or<ISyntax>(Name)
-            .Or<ISyntax>(Class)
-            .Or<ISyntax>(Property)
-            .Or<ISyntax>(Child)
-            .Or<ISyntax>(Template)
-            .Or<ISyntax>(Descendant);
-
-        public static readonly Parser<IEnumerable<ISyntax>> Selector = SingleSelector.Many().End();
-        
         public interface ISyntax
         {
         }
 
-        public class OfTypeSyntax : ISyntax
+        public interface ITypeSyntax
+        {
+            string TypeName { get; set; }
+
+            string Xmlns { get; set; }
+        }
+
+        public class OfTypeSyntax : ISyntax, ITypeSyntax
         {
             public string TypeName { get; set; }
 
@@ -136,7 +299,7 @@ namespace Avalonia.Markup.Parsers
             }
         }
 
-        public class IsSyntax : ISyntax
+        public class IsSyntax : ISyntax, ITypeSyntax
         {
             public string TypeName { get; set; }
 

+ 54 - 63
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@@ -2,7 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using System.Globalization;
+using Avalonia.Data.Core;
 using Avalonia.Styling;
 using Avalonia.Utilities;
 using Sprache;
@@ -36,79 +38,68 @@ namespace Avalonia.Markup.Parsers
         /// <returns>The parsed selector.</returns>
         public Selector Parse(string s)
         {
-            var syntax = SelectorGrammar.Selector.Parse(s);
+            var syntax = SelectorGrammar.Parse(s);
             var result = default(Selector);
 
             foreach (var i in syntax)
             {
-                var ofType = i as SelectorGrammar.OfTypeSyntax;
-                var @is = i as SelectorGrammar.IsSyntax;
-                var @class = i as SelectorGrammar.ClassSyntax;
-                var name = i as SelectorGrammar.NameSyntax;
-                var property = i as SelectorGrammar.PropertySyntax;
-                var child = i as SelectorGrammar.ChildSyntax;
-                var descendant = i as SelectorGrammar.DescendantSyntax;
-                var template = i as SelectorGrammar.TemplateSyntax;
-
-                if (ofType != null)
-                {
-                    result = result.OfType(_typeResolver(ofType.Xmlns, ofType.TypeName));
-                }
-                if (@is != null)
-                {
-                    result = result.Is(_typeResolver(@is.Xmlns, @is.TypeName));
-                }
-                else if (@class != null)
-                {
-                    result = result.Class(@class.Class);
-                }
-                else if (name != null)
-                {
-                    result = result.Name(name.Name);
-                }
-                else if (property != null)
+                switch (i)
                 {
-                    var type = result?.TargetType;
 
-                    if (type == null)
-                    {
-                        throw new InvalidOperationException("Property selectors must be applied to a type.");
-                    }
+                    case SelectorGrammar.OfTypeSyntax ofType:
+                        result = result.OfType(_typeResolver(ofType.Xmlns, ofType.TypeName));
+                        break;
+                    case SelectorGrammar.IsSyntax @is:
+                        result = result.Is(_typeResolver(@is.Xmlns, @is.TypeName));
+                        break;
+                    case SelectorGrammar.ClassSyntax @class:
+                        result = result.Class(@class.Class);
+                        break;
+                    case SelectorGrammar.NameSyntax name:
+                        result = result.Name(name.Name);
+                        break;
+                    case SelectorGrammar.PropertySyntax property:
+                        {
+                            var type = result?.TargetType;
 
-                    var targetProperty = AvaloniaPropertyRegistry.Instance.FindRegistered(type, property.Property);
+                            if (type == null)
+                            {
+                                throw new InvalidOperationException("Property selectors must be applied to a type.");
+                            }
 
-                    if (targetProperty == null)
-                    {
-                        throw new InvalidOperationException($"Cannot find '{property.Property}' on '{type}");
-                    }
+                            var targetProperty = AvaloniaPropertyRegistry.Instance.FindRegistered(type, property.Property);
 
-                    object typedValue;
+                            if (targetProperty == null)
+                            {
+                                throw new InvalidOperationException($"Cannot find '{property.Property}' on '{type}");
+                            }
 
-                    if (TypeUtilities.TryConvert(
-                            targetProperty.PropertyType, 
-                            property.Value, 
-                            CultureInfo.InvariantCulture,
-                            out typedValue))
-                    {
-                        result = result.PropertyEquals(targetProperty, typedValue);
-                    }
-                    else
-                    {
-                        throw new InvalidOperationException(
-                            $"Could not convert '{property.Value}' to '{targetProperty.PropertyType}");
-                    }
-                }
-                else if (child != null)
-                {
-                    result = result.Child();
-                }
-                else if (descendant != null)
-                {
-                    result = result.Descendant();
-                }
-                else if (template != null)
-                {
-                    result = result.Template();
+                            object typedValue;
+
+                            if (TypeUtilities.TryConvert(
+                                    targetProperty.PropertyType,
+                                    property.Value,
+                                    CultureInfo.InvariantCulture,
+                                    out typedValue))
+                            {
+                                result = result.PropertyEquals(targetProperty, typedValue);
+                            }
+                            else
+                            {
+                                throw new InvalidOperationException(
+                                    $"Could not convert '{property.Value}' to '{targetProperty.PropertyType}");
+                            }
+                            break;
+                        }
+                    case SelectorGrammar.ChildSyntax child:
+                        result = result.Child();
+                        break;
+                    case SelectorGrammar.DescendantSyntax descendant:
+                        result = result.Descendant();
+                        break;
+                    case SelectorGrammar.TemplateSyntax template:
+                        result = result.Template();
+                        break;
                 }
             }
 

+ 20 - 20
tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

@@ -2,8 +2,8 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System.Linq;
+using Avalonia.Data.Core;
 using Avalonia.Markup.Parsers;
-using Sprache;
 using Xunit;
 
 namespace Avalonia.Markup.UnitTests.Parsers
@@ -13,7 +13,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType()
         {
-            var result = SelectorGrammar.Selector.Parse("Button").ToList();
+            var result = SelectorGrammar.Parse("Button");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = null } },
@@ -23,7 +23,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void NamespacedOfType()
         {
-            var result = SelectorGrammar.Selector.Parse("x|Button").ToList();
+            var result = SelectorGrammar.Parse("x|Button");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = "x" } },
@@ -33,7 +33,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Name()
         {
-            var result = SelectorGrammar.Selector.Parse("#foo").ToList();
+            var result = SelectorGrammar.Parse("#foo");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.NameSyntax { Name = "foo" }, },
@@ -43,7 +43,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Name()
         {
-            var result = SelectorGrammar.Selector.Parse("Button#foo").ToList();
+            var result = SelectorGrammar.Parse("Button#foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -57,7 +57,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Is()
         {
-            var result = SelectorGrammar.Selector.Parse(":is(Button)").ToList();
+            var result = SelectorGrammar.Parse(":is(Button)");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.IsSyntax { TypeName = "Button", Xmlns = null } },
@@ -67,7 +67,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Is_Name()
         {
-            var result = SelectorGrammar.Selector.Parse(":is(Button)#foo").ToList();
+            var result = SelectorGrammar.Parse(":is(Button)#foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -81,7 +81,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void NamespacedIs_Name()
         {
-            var result = SelectorGrammar.Selector.Parse(":is(x|Button)#foo").ToList();
+            var result = SelectorGrammar.Parse(":is(x|Button)#foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -95,7 +95,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Class()
         {
-            var result = SelectorGrammar.Selector.Parse(".foo").ToList();
+            var result = SelectorGrammar.Parse(".foo");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.ClassSyntax { Class = "foo" } },
@@ -105,7 +105,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Pseudoclass()
         {
-            var result = SelectorGrammar.Selector.Parse(":foo").ToList();
+            var result = SelectorGrammar.Parse(":foo");
 
             Assert.Equal(
                 new[] { new SelectorGrammar.ClassSyntax { Class = ":foo" } },
@@ -115,7 +115,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Class()
         {
-            var result = SelectorGrammar.Selector.Parse("Button.foo").ToList();
+            var result = SelectorGrammar.Parse("Button.foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[] 
@@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Child_Class()
         {
-            var result = SelectorGrammar.Selector.Parse("Button > .foo").ToList();
+            var result = SelectorGrammar.Parse("Button > .foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -144,7 +144,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Child_Class_No_Spaces()
         {
-            var result = SelectorGrammar.Selector.Parse("Button>.foo").ToList();
+            var result = SelectorGrammar.Parse("Button>.foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -159,7 +159,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Descendant_Class()
         {
-            var result = SelectorGrammar.Selector.Parse("Button .foo").ToList();
+            var result = SelectorGrammar.Parse("Button .foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -174,7 +174,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Template_Class()
         {
-            var result = SelectorGrammar.Selector.Parse("Button /template/ .foo").ToList();
+            var result = SelectorGrammar.Parse("Button /template/ .foo");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -189,7 +189,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void OfType_Property()
         {
-            var result = SelectorGrammar.Selector.Parse("Button[Foo=bar]").ToList();
+            var result = SelectorGrammar.Parse("Button[Foo=bar]");
 
             Assert.Equal(
                 new SelectorGrammar.ISyntax[]
@@ -203,25 +203,25 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Namespace_Alone_Fails()
         {
-            Assert.Throws<ParseException>(() => SelectorGrammar.Selector.Parse("ns|").ToList());
+            Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse("ns|"));
         }
 
         [Fact]
         public void Dot_Alone_Fails()
         {
-            Assert.Throws<ParseException>(() => SelectorGrammar.Selector.Parse(". dot").ToList());
+            Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse(". dot"));
         }
 
         [Fact]
         public void Invalid_Identifier_Fails()
         {
-            Assert.Throws<ParseException>(() => SelectorGrammar.Selector.Parse("%foo").ToList());
+            Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse("%foo"));
         }
 
         [Fact]
         public void Invalid_Class_Fails()
         {
-            Assert.Throws<ParseException>(() => SelectorGrammar.Selector.Parse(".%foo").ToList());
+            Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse(".%foo"));
         }
     }
 }

+ 3 - 1
tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs

@@ -1,6 +1,8 @@
 using System;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
 using Avalonia.Markup.Parsers;
+using Avalonia.Styling;
 using Xunit;
 
 namespace Avalonia.Markup.UnitTests.Parsers
@@ -10,7 +12,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
         [Fact]
         public void Parses_Boolean_Property_Selector()
         {
-            var target = new SelectorParser((type, ns) => typeof(TextBlock));
+            var target = new SelectorParser((ns, type) => typeof(TextBlock));
             var result = target.Parse("TextBlock[IsPointerOver=True]");
         }
     }