Browse Source

Make Reader a ref struct and have it operate directly on ReadOnlySpans to use less memory.

pull/1668/head
Jeremy Koritzinsky 8 years ago
parent
commit
841efbcfce
  1. 4
      src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
  2. 4
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  3. 48
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  4. 47
      src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs
  5. 82
      src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs
  6. 98
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

4
src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs

@ -10,7 +10,7 @@ namespace Avalonia.Markup.Parsers
{
internal static class ArgumentListParser
{
public static IList<string> Parse(Reader r, char open, char close)
public static IList<string> ParseArguments(this ref Reader r, char open, char close)
{
if (r.Peek == open)
{
@ -20,7 +20,7 @@ namespace Avalonia.Markup.Parsers
while (!r.End)
{
var argument = r.TakeUntil(',');
var argument = r.TakeWhile(c => c != ',' && c != close);
if (argument.IsEmpty)
{
throw new ExpressionParseException(r.Position, "Expected indexer argument.");

4
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs

@ -15,9 +15,9 @@ namespace Avalonia.Markup.Parsers
return new EmptyExpressionNode();
}
var reader = new Reader(expression);
var reader = new Reader(expression.AsSpan());
var parser = new ExpressionParser(enableValidation, typeResolver);
var node = parser.Parse(reader);
var node = parser.Parse(ref reader);
if (!reader.End)
{

48
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@ -20,7 +20,7 @@ namespace Avalonia.Markup.Parsers
_enableValidation = enableValidation;
}
public ExpressionNode Parse(Reader r)
public ExpressionNode Parse(ref Reader r)
{
var nodes = new List<ExpressionNode>();
var state = State.Start;
@ -30,23 +30,23 @@ namespace Avalonia.Markup.Parsers
switch (state)
{
case State.Start:
state = ParseStart(r, nodes);
state = ParseStart(ref r, nodes);
break;
case State.AfterMember:
state = ParseAfterMember(r, nodes);
state = ParseAfterMember(ref r, nodes);
break;
case State.BeforeMember:
state = ParseBeforeMember(r, nodes);
state = ParseBeforeMember(ref r, nodes);
break;
case State.AttachedProperty:
state = ParseAttachedProperty(r, nodes);
state = ParseAttachedProperty(ref r, nodes);
break;
case State.Indexer:
state = ParseIndexer(r, nodes);
state = ParseIndexer(ref r, nodes);
break;
}
}
@ -64,18 +64,18 @@ namespace Avalonia.Markup.Parsers
return nodes.FirstOrDefault();
}
private State ParseStart(Reader r, IList<ExpressionNode> nodes)
private State ParseStart(ref Reader r, IList<ExpressionNode> nodes)
{
if (ParseNot(r))
if (ParseNot(ref r))
{
nodes.Add(new LogicalNotNode());
return State.Start;
}
else if (ParseOpenBrace(r))
else if (ParseOpenBrace(ref r))
{
return State.AttachedProperty;
}
else if (PeekOpenBracket(r))
else if (PeekOpenBracket(ref r))
{
return State.Indexer;
}
@ -93,18 +93,18 @@ namespace Avalonia.Markup.Parsers
return State.End;
}
private static State ParseAfterMember(Reader r, IList<ExpressionNode> nodes)
private static State ParseAfterMember(ref Reader r, IList<ExpressionNode> nodes)
{
if (ParseMemberAccessor(r))
if (ParseMemberAccessor(ref r))
{
return State.BeforeMember;
}
else if (ParseStreamOperator(r))
else if (ParseStreamOperator(ref r))
{
nodes.Add(new StreamNode());
return State.AfterMember;
}
else if (PeekOpenBracket(r))
else if (PeekOpenBracket(ref r))
{
return State.Indexer;
}
@ -112,9 +112,9 @@ namespace Avalonia.Markup.Parsers
return State.End;
}
private State ParseBeforeMember(Reader r, IList<ExpressionNode> nodes)
private State ParseBeforeMember(ref Reader r, IList<ExpressionNode> nodes)
{
if (ParseOpenBrace(r))
if (ParseOpenBrace(ref r))
{
return State.AttachedProperty;
}
@ -132,7 +132,7 @@ namespace Avalonia.Markup.Parsers
}
}
private State ParseAttachedProperty(Reader r, List<ExpressionNode> nodes)
private State ParseAttachedProperty(ref Reader r, List<ExpressionNode> nodes)
{
ReadOnlySpan<char> ns = ReadOnlySpan<char>.Empty;
ReadOnlySpan<char> owner;
@ -166,9 +166,9 @@ namespace Avalonia.Markup.Parsers
return State.AfterMember;
}
private State ParseIndexer(Reader r, List<ExpressionNode> nodes)
private State ParseIndexer(ref Reader r, List<ExpressionNode> nodes)
{
var args = ArgumentListParser.Parse(r, '[', ']');
var args = r.ParseArguments('[', ']');
if (args.Count == 0)
{
@ -179,27 +179,27 @@ namespace Avalonia.Markup.Parsers
return State.AfterMember;
}
private static bool ParseNot(Reader r)
private static bool ParseNot(ref Reader r)
{
return !r.End && r.TakeIf('!');
}
private static bool ParseMemberAccessor(Reader r)
private static bool ParseMemberAccessor(ref Reader r)
{
return !r.End && r.TakeIf('.');
}
private static bool ParseOpenBrace(Reader r)
private static bool ParseOpenBrace(ref Reader r)
{
return !r.End && r.TakeIf('(');
}
private static bool PeekOpenBracket(Reader r)
private static bool PeekOpenBracket(ref Reader r)
{
return !r.End && r.Peek == '[';
}
private static bool ParseStreamOperator(Reader r)
private static bool ParseStreamOperator(ref Reader r)
{
return !r.End && r.TakeIf('^');
}

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

@ -0,0 +1,47 @@
// 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 System.Text;
namespace Avalonia.Markup.Parsers
{
internal static class IdentifierParser
{
public static ReadOnlySpan<char> ParseIdentifier(this ref Reader r)
{
if (IsValidIdentifierStart(r.Peek))
{
return r.TakeWhile(IsValidIdentifierChar);
}
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;
}
}
}
}

82
src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs

@ -7,27 +7,32 @@ using System.Text;
namespace Avalonia.Markup.Parsers
{
internal class Reader
internal ref struct Reader
{
private readonly string _s;
private int _i;
private ReadOnlySpan<char> _s;
public Reader(string s)
public Reader(ReadOnlySpan<char> s)
:this()
{
_s = s;
}
public bool End => _i == _s.Length;
public char Peek => _s[_i];
public int Position => _i;
public char Take() => _s[_i++];
public bool End => _s.IsEmpty;
public char Peek => _s[0];
public int Position { get; private set; }
public char Take()
{
Position++;
char taken = _s[0];
_s = _s.Slice(1);
return taken;
}
public void SkipWhitespace()
{
while (!End && char.IsWhiteSpace(Peek))
{
Take();
}
var trimmed = _s.TrimStart();
Position += _s.Length - trimmed.Length;
_s = trimmed;
}
public bool TakeIf(char c)
@ -55,53 +60,26 @@ namespace Avalonia.Markup.Parsers
public ReadOnlySpan<char> TakeUntil(char c)
{
int startIndex = Position;
while (!End && Peek != c)
int len;
for (len = 0; len < _s.Length && _s[len] != c; len++)
{
Take();
}
return _s.AsSpan(startIndex, Position - startIndex);
var span = _s.Slice(0, len);
_s = _s.Slice(len);
Position += len;
return span;
}
public ReadOnlySpan<char> ParseIdentifier()
public ReadOnlySpan<char> TakeWhile(Func<char, bool> condition)
{
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
int len;
for (len = 0; len < _s.Length && condition(_s[len]); len++)
{
var cat = CharUnicodeInfo.GetUnicodeCategory(c);
return cat == UnicodeCategory.NonSpacingMark ||
cat == UnicodeCategory.SpacingCombiningMark ||
cat == UnicodeCategory.ConnectorPunctuation ||
cat == UnicodeCategory.Format ||
cat == UnicodeCategory.DecimalDigitNumber;
}
var span = _s.Slice(0, len);
_s = _s.Slice(len);
Position += len;
return span;
}
}
}

98
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -31,7 +31,7 @@ namespace Avalonia.Markup.Parsers
public static IEnumerable<ISyntax> Parse(string s)
{
var r = new Reader(s);
var r = new Reader(s.AsSpan());
var state = State.Start;
var selector = new List<ISyntax>();
while (!r.End && state != State.End)
@ -40,34 +40,34 @@ namespace Avalonia.Markup.Parsers
switch (state)
{
case State.Start:
(state, syntax) = ParseStart(r);
state = ParseStart(ref r);
break;
case State.Middle:
(state, syntax) = ParseMiddle(r);
state = ParseMiddle(ref r);
break;
case State.CanHaveType:
state = ParseCanHaveType(ref r);
break;
case State.Colon:
(state, syntax) = ParseColon(r);
(state, syntax) = ParseColon(ref r);
break;
case State.Class:
(state, syntax) = ParseClass(r);
(state, syntax) = ParseClass(ref r);
break;
case State.Traversal:
(state, syntax) = ParseTraversal(r);
(state, syntax) = ParseTraversal(ref r);
break;
case State.TypeName:
(state, syntax) = ParseTypeName(r);
break;
case State.CanHaveType:
(state, syntax) = ParseCanHaveType(r);
(state, syntax) = ParseTypeName(ref r);
break;
case State.Property:
(state, syntax) = ParseProperty(r);
(state, syntax) = ParseProperty(ref r);
break;
case State.Template:
(state, syntax) = ParseTemplate(r);
(state, syntax) = ParseTemplate(ref r);
break;
case State.Name:
(state, syntax) = ParseName(r);
(state, syntax) = ParseName(ref r);
break;
}
if (syntax != null)
@ -84,59 +84,64 @@ namespace Avalonia.Markup.Parsers
return selector;
}
private static (State, ISyntax) ParseStart(Reader r)
private static State ParseStart(ref Reader r)
{
r.SkipWhitespace();
if (r.End)
{
return State.End;
}
if (r.TakeIf(':'))
{
return (State.Colon, null);
return State.Colon;
}
else if (r.TakeIf('.'))
{
return (State.Class, null);
return State.Class;
}
else if (r.TakeIf('#'))
{
return (State.Name, null);
return State.Name;
}
return (State.TypeName, null);
return State.TypeName;
}
private static (State, ISyntax) ParseMiddle(Reader r)
private static State ParseMiddle(ref Reader r)
{
if (r.TakeIf(':'))
{
return (State.Colon, null);
return State.Colon;
}
else if (r.TakeIf('.'))
{
return (State.Class, null);
return State.Class;
}
else if (r.TakeIf(char.IsWhiteSpace) || r.Peek == '>')
{
return (State.Traversal, null);
return State.Traversal;
}
else if (r.TakeIf('/'))
{
return (State.Template, null);
return State.Template;
}
else if (r.TakeIf('#'))
{
return (State.Name, null);
return State.Name;
}
return (State.TypeName, null);
return State.TypeName;
}
private static (State, ISyntax) ParseCanHaveType(Reader r)
private static State ParseCanHaveType(ref Reader r)
{
if (r.TakeIf('['))
{
return (State.Property, null);
return State.Property;
}
return (State.Middle, null);
return State.Middle;
}
private static (State, ISyntax) ParseColon(Reader r)
private static (State, ISyntax) ParseColon(ref Reader r)
{
var identifier = r.ParseIdentifier();
@ -145,9 +150,10 @@ namespace Avalonia.Markup.Parsers
throw new ExpressionParseException(r.Position, "Expected class name or is selector after ':'.");
}
if (identifier.SequenceEqual("is".AsSpan()) && r.TakeIf('('))
const string IsKeyword = "is";
if (identifier.SequenceEqual(IsKeyword.AsSpan()) && r.TakeIf('('))
{
var syntax = ParseType<IsSyntax>(r);
var syntax = ParseType(ref r, new IsSyntax());
if (r.End || !r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, $"Expected ')', got {r.Peek}");
@ -166,7 +172,7 @@ namespace Avalonia.Markup.Parsers
}
}
private static (State, ISyntax) ParseTraversal(Reader r)
private static (State, ISyntax) ParseTraversal(ref Reader r)
{
r.SkipWhitespace();
if (r.TakeIf('>'))
@ -188,7 +194,7 @@ namespace Avalonia.Markup.Parsers
}
}
private static (State, ISyntax) ParseClass(Reader r)
private static (State, ISyntax) ParseClass(ref Reader r)
{
var @class = r.ParseIdentifier();
if (@class.IsEmpty)
@ -199,10 +205,11 @@ namespace Avalonia.Markup.Parsers
return (State.CanHaveType, new ClassSyntax { Class = @class.ToString() });
}
private static (State, ISyntax) ParseTemplate(Reader r)
private static (State, ISyntax) ParseTemplate(ref Reader r)
{
var template = r.ParseIdentifier();
if (!template.SequenceEqual(nameof(template).AsSpan()))
const string TemplateKeyword = "template";
if (!template.SequenceEqual(TemplateKeyword.AsSpan()))
{
throw new ExpressionParseException(r.Position, $"Expected 'template', got '{template.ToString()}'");
}
@ -213,7 +220,7 @@ namespace Avalonia.Markup.Parsers
return (State.Start, new TemplateSyntax());
}
private static (State, ISyntax) ParseName(Reader r)
private static (State, ISyntax) ParseName(ref Reader r)
{
var name = r.ParseIdentifier();
if (name.IsEmpty)
@ -223,12 +230,12 @@ namespace Avalonia.Markup.Parsers
return (State.CanHaveType, new NameSyntax { Name = name.ToString() });
}
private static (State, ISyntax) ParseTypeName(Reader r)
private static (State, ISyntax) ParseTypeName(ref Reader r)
{
return (State.CanHaveType, ParseType<OfTypeSyntax>(r));
return (State.CanHaveType, ParseType(ref r, new OfTypeSyntax()));
}
private static (State, ISyntax) ParseProperty(Reader r)
private static (State, ISyntax) ParseProperty(ref Reader r)
{
var property = r.ParseIdentifier();
@ -244,8 +251,8 @@ namespace Avalonia.Markup.Parsers
return (State.CanHaveType, new PropertySyntax { Property = property.ToString(), Value = value.ToString() });
}
private static TSyntax ParseType<TSyntax>(Reader r)
where TSyntax : ITypeSyntax, new()
private static TSyntax ParseType<TSyntax>(ref Reader r, TSyntax syntax)
where TSyntax : ITypeSyntax
{
ReadOnlySpan<char> ns = null;
ReadOnlySpan<char> type;
@ -269,11 +276,10 @@ namespace Avalonia.Markup.Parsers
{
type = namespaceOrTypeName;
}
return new TSyntax
{
Xmlns = ns.ToString(),
TypeName = type.ToString()
};
syntax.Xmlns = ns.ToString();
syntax.TypeName = type.ToString();
return syntax;
}
public interface ISyntax

Loading…
Cancel
Save