Browse Source

Span-ify our homebrew parser's 'public' API.

pull/1668/head
Jeremy Koritzinsky 8 years ago
parent
commit
4040216ff8
  1. 1
      Avalonia.sln
  2. 5
      build/System.Memory.props
  3. 4
      packages.cake
  4. 1
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  5. 11
      src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
  6. 14
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  7. 52
      src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs
  8. 50
      src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs
  9. 35
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  10. 4
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

1
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

5
build/System.Memory.props

@ -0,0 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.0" />
</ItemGroup>
</Project>

4
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",

1
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -9,4 +9,5 @@
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\System.Memory.props" />
</Project>

11
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();

14
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<ExpressionNode> nodes)
{
string ns = string.Empty;
string owner;
ReadOnlySpan<char> ns = ReadOnlySpan<char>.Empty;
ReadOnlySpan<char> 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;

52
src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs

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

50
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<char> 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<char> ParseIdentifier()
{
if (IsValidIdentifierStart(Peek))
{
int startIndex = Position;
while (!End && IsValidIdentifierChar(Peek))
{
Take();
}
return _s.AsSpan(startIndex, Position - startIndex);
}
else
{
return ReadOnlySpan<char>.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();
}
}
}

35
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<IsSyntax>(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<TSyntax>(Reader r)
where TSyntax : ITypeSyntax, new()
{
string ns = null;
string type;
ReadOnlySpan<char> ns = null;
ReadOnlySpan<char> 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)
{

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

Loading…
Cancel
Save