From 4040216ff865714abe6623d67cd6146685c67a23 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 9 Jun 2018 17:47:08 -0500 Subject: [PATCH] Span-ify our homebrew parser's 'public' API. --- Avalonia.sln | 1 + build/System.Memory.props | 5 ++ packages.cake | 4 ++ .../Avalonia.Markup/Avalonia.Markup.csproj | 1 + .../Markup/Parsers/ArgumentListParser.cs | 11 ++-- .../Markup/Parsers/ExpressionParser.cs | 14 ++--- .../Markup/Parsers/IdentifierParser.cs | 52 ------------------- .../Avalonia.Markup/Markup/Parsers/Reader.cs | 50 ++++++++++++++++-- .../Markup/Parsers/SelectorGrammar.cs | 35 +++++++------ .../Parsers/SelectorGrammarTests.cs | 4 +- 10 files changed, 88 insertions(+), 89 deletions(-) create mode 100644 build/System.Memory.props delete mode 100644 src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs diff --git a/Avalonia.sln b/Avalonia.sln index e65143c6df..54ffa00d92 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -149,6 +149,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\SharpDX.props = build\SharpDX.props build\SkiaSharp.props = build\SkiaSharp.props build\Splat.props = build\Splat.props + build\System.Memory.props = build\System.Memory.props build\XUnit.props = build\XUnit.props EndProjectSection EndProject diff --git a/build/System.Memory.props b/build/System.Memory.props new file mode 100644 index 0000000000..f3253f8882 --- /dev/null +++ b/build/System.Memory.props @@ -0,0 +1,5 @@ + + + + + diff --git a/packages.cake b/packages.cake index 5d20d974bb..84a04b7238 100644 --- a/packages.cake +++ b/packages.cake @@ -119,6 +119,7 @@ public class Packages var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1; var SharpDXDirect3D9Version = packageVersions["SharpDX.Direct3D9"].FirstOrDefault().Item1; var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1; + var SystemMemoryVersion = packageVersions["System.Memory"].FirstOrDefault().Item1; context.Information("Package: Serilog, version: {0}", SerilogVersion); context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion); @@ -131,6 +132,7 @@ public class Packages context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version); context.Information("Package: SharpDX.Direct3D9, version: {0}", SharpDXDirect3D9Version); context.Information("Package: SharpDX.DXGI, version: {0}", SharpDXDXGIVersion); + context.Information("Package: System.Memory, version: {0}", SystemMemoryVersion); var nugetPackagesDir = System.Environment.GetEnvironmentVariable("NUGET_HOME") ?? System.IO.Path.Combine(System.Environment.GetEnvironmentVariable("USERPROFILE") ?? System.Environment.GetEnvironmentVariable("HOME"), ".nuget"); @@ -235,6 +237,7 @@ public class Packages new NuSpecDependency() { Id = "Serilog.Sinks.Trace", Version = SerilogSinksTraceVersion }, new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion }, new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version }, + new NuSpecDependency() { Id = "System.Memory", Version = SystemMemoryVersion }, //.NET Core new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" }, new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" }, @@ -244,6 +247,7 @@ public class Packages new NuSpecDependency() { Id = "Serilog.Sinks.Trace", TargetFramework = "netcoreapp2.0", Version = SerilogSinksTraceVersion }, new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion }, new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", TargetFramework = "netcoreapp2.0", Version = parameters.Version }, + new NuSpecDependency() { Id = "System.Memory", TargetFramework = "netcoreapp2.0", Version = SystemMemoryVersion }, } .Deps(new string[]{null, "netcoreapp2.0"}, "System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives", diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 9180e31039..41ffaedef4 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -9,4 +9,5 @@ + \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs index ae48657c01..d3f850fbf6 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs @@ -20,16 +20,13 @@ namespace Avalonia.Markup.Parsers while (!r.End) { - var builder = new StringBuilder(); - while (!r.End && r.Peek != ',' && r.Peek != close && !char.IsWhiteSpace(r.Peek)) - { - builder.Append(r.Take()); - } - if (builder.Length == 0) + var argument = r.TakeUntil(','); + if (argument.IsEmpty) { throw new ExpressionParseException(r.Position, "Expected indexer argument."); } - result.Add(builder.ToString()); + + result.Add(argument.ToString()); r.SkipWhitespace(); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs index 8f609ba2bd..9ac70df56b 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs @@ -83,9 +83,9 @@ namespace Avalonia.Markup.Parsers { var identifier = r.ParseIdentifier(); - if (identifier != null) + if (!identifier.IsEmpty) { - nodes.Add(new PropertyAccessorNode(identifier, _enableValidation)); + nodes.Add(new PropertyAccessorNode(identifier.ToString(), _enableValidation)); return State.AfterMember; } } @@ -122,9 +122,9 @@ namespace Avalonia.Markup.Parsers { var identifier = r.ParseIdentifier(); - if (identifier != null) + if (!identifier.IsEmpty) { - nodes.Add(new PropertyAccessorNode(identifier, _enableValidation)); + nodes.Add(new PropertyAccessorNode(identifier.ToString(), _enableValidation)); return State.AfterMember; } @@ -134,8 +134,8 @@ namespace Avalonia.Markup.Parsers private State ParseAttachedProperty(Reader r, List nodes) { - string ns = string.Empty; - string owner; + ReadOnlySpan ns = ReadOnlySpan.Empty; + ReadOnlySpan owner; var ownerOrNamespace = r.ParseIdentifier(); if (r.TakeIf(':')) @@ -160,7 +160,7 @@ namespace Avalonia.Markup.Parsers throw new ExpressionParseException(r.Position, "Expected ')'."); } - var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(ns, owner), name); + var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(ns.ToString(), owner.ToString()), name.ToString()); nodes.Add(new AvaloniaPropertyAccessorNode(property, _enableValidation)); return State.AfterMember; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs deleted file mode 100644 index 470a4c8203..0000000000 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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 System.Globalization; -using System.Text; - -namespace Avalonia.Markup.Parsers -{ - internal static class IdentifierParser - { - public static string ParseIdentifier(this Reader r) - { - if (IsValidIdentifierStart(r.Peek)) - { - var result = new StringBuilder(); - - while (!r.End && IsValidIdentifierChar(r.Peek)) - { - result.Append(r.Take()); - } - - return result.ToString(); - } - else - { - return null; - } - } - - private static bool IsValidIdentifierStart(char c) - { - return char.IsLetter(c) || c == '_'; - } - - private static bool IsValidIdentifierChar(char c) - { - if (IsValidIdentifierStart(c)) - { - return true; - } - else - { - var cat = CharUnicodeInfo.GetUnicodeCategory(c); - return cat == UnicodeCategory.NonSpacingMark || - cat == UnicodeCategory.SpacingCombiningMark || - cat == UnicodeCategory.ConnectorPunctuation || - cat == UnicodeCategory.Format || - cat == UnicodeCategory.DecimalDigitNumber; - } - } - } -} diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs index 6511773bea..82a02b10ab 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs +++ b/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.Globalization; using System.Text; namespace Avalonia.Markup.Parsers @@ -52,14 +53,55 @@ namespace Avalonia.Markup.Parsers return false; } - public string TakeUntil(char c) + public ReadOnlySpan TakeUntil(char c) { - var builder = new StringBuilder(); + int startIndex = Position; while (!End && Peek != c) { - builder.Append(Take()); + Take(); + } + return _s.AsSpan(startIndex, Position - startIndex); + } + + public ReadOnlySpan ParseIdentifier() + { + if (IsValidIdentifierStart(Peek)) + { + int startIndex = Position; + + while (!End && IsValidIdentifierChar(Peek)) + { + Take(); + } + + return _s.AsSpan(startIndex, Position - startIndex); + } + else + { + return ReadOnlySpan.Empty; + } + } + + private static bool IsValidIdentifierStart(char c) + { + return char.IsLetter(c) || c == '_'; + } + + private static bool IsValidIdentifierChar(char c) + { + if (IsValidIdentifierStart(c)) + { + return true; + } + else + { + var cat = CharUnicodeInfo.GetUnicodeCategory(c); + return cat == UnicodeCategory.NonSpacingMark || + cat == UnicodeCategory.SpacingCombiningMark || + cat == UnicodeCategory.ConnectorPunctuation || + cat == UnicodeCategory.Format || + cat == UnicodeCategory.DecimalDigitNumber; } - return builder.ToString(); } } } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 20e49341b7..02123b11f2 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -1,6 +1,7 @@ // 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 System; using System.Collections.Generic; using System.Globalization; using Avalonia.Data.Core; @@ -139,12 +140,12 @@ namespace Avalonia.Markup.Parsers { var identifier = r.ParseIdentifier(); - if (string.IsNullOrEmpty(identifier)) + if (identifier.IsEmpty) { throw new ExpressionParseException(r.Position, "Expected class name or is selector after ':'."); } - if (identifier == "is" && r.TakeIf('(')) + if (identifier.SequenceEqual("is".AsSpan()) && r.TakeIf('(')) { var syntax = ParseType(r); if (r.End || !r.TakeIf(')')) @@ -160,7 +161,7 @@ namespace Avalonia.Markup.Parsers State.CanHaveType, new ClassSyntax { - Class = ":" + identifier + Class = ":" + identifier.ToString() }); } } @@ -190,20 +191,20 @@ namespace Avalonia.Markup.Parsers private static (State, ISyntax) ParseClass(Reader r) { var @class = r.ParseIdentifier(); - if (string.IsNullOrEmpty(@class)) + if (@class.IsEmpty) { throw new ExpressionParseException(r.Position, $"Expected a class name after '.'."); } - return (State.CanHaveType, new ClassSyntax { Class = @class }); + return (State.CanHaveType, new ClassSyntax { Class = @class.ToString() }); } private static (State, ISyntax) ParseTemplate(Reader r) { var template = r.ParseIdentifier(); - if (template != nameof(template)) + if (!template.SequenceEqual(nameof(template).AsSpan())) { - throw new ExpressionParseException(r.Position, $"Expected 'template', got {template}"); + throw new ExpressionParseException(r.Position, $"Expected 'template', got '{template.ToString()}'"); } else if (!r.TakeIf('/')) { @@ -215,11 +216,11 @@ namespace Avalonia.Markup.Parsers private static (State, ISyntax) ParseName(Reader r) { var name = r.ParseIdentifier(); - if (string.IsNullOrEmpty(name)) + if (name.IsEmpty) { throw new ExpressionParseException(r.Position, $"Expected a name after '#'."); } - return (State.CanHaveType, new NameSyntax { Name = name }); + return (State.CanHaveType, new NameSyntax { Name = name.ToString() }); } private static (State, ISyntax) ParseTypeName(Reader r) @@ -240,17 +241,17 @@ namespace Avalonia.Markup.Parsers r.Take(); - return (State.CanHaveType, new PropertySyntax { Property = property, Value = value }); + return (State.CanHaveType, new PropertySyntax { Property = property.ToString(), Value = value.ToString() }); } private static TSyntax ParseType(Reader r) where TSyntax : ITypeSyntax, new() { - string ns = null; - string type; + ReadOnlySpan ns = null; + ReadOnlySpan type; var namespaceOrTypeName = r.ParseIdentifier(); - if (string.IsNullOrEmpty(namespaceOrTypeName)) + if (namespaceOrTypeName.IsEmpty) { throw new ExpressionParseException(r.Position, $"Expected an identifier, got '{r.Peek}"); } @@ -270,8 +271,8 @@ namespace Avalonia.Markup.Parsers } return new TSyntax { - Xmlns = ns, - TypeName = type + Xmlns = ns.ToString(), + TypeName = type.ToString() }; } @@ -290,7 +291,7 @@ namespace Avalonia.Markup.Parsers { public string TypeName { get; set; } - public string Xmlns { get; set; } + public string Xmlns { get; set; } = string.Empty; public override bool Equals(object obj) { @@ -303,7 +304,7 @@ namespace Avalonia.Markup.Parsers { public string TypeName { get; set; } - public string Xmlns { get; set; } + public string Xmlns { get; set; } = string.Empty; public override bool Equals(object obj) { diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs index dc0fd94b36..88fe5a2a12 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs @@ -16,7 +16,7 @@ namespace Avalonia.Markup.UnitTests.Parsers var result = SelectorGrammar.Parse("Button"); Assert.Equal( - new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = null } }, + new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = "" } }, result); } @@ -60,7 +60,7 @@ namespace Avalonia.Markup.UnitTests.Parsers var result = SelectorGrammar.Parse(":is(Button)"); Assert.Equal( - new[] { new SelectorGrammar.IsSyntax { TypeName = "Button", Xmlns = null } }, + new[] { new SelectorGrammar.IsSyntax { TypeName = "Button", Xmlns = "" } }, result); }