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);
}