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 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) if (r.Peek == open)
{ {
@ -20,7 +20,7 @@ namespace Avalonia.Markup.Parsers
while (!r.End) while (!r.End)
{ {
var argument = r.TakeUntil(','); var argument = r.TakeWhile(c => c != ',' && c != close);
if (argument.IsEmpty) if (argument.IsEmpty)
{ {
throw new ExpressionParseException(r.Position, "Expected indexer argument."); 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(); return new EmptyExpressionNode();
} }
var reader = new Reader(expression); var reader = new Reader(expression.AsSpan());
var parser = new ExpressionParser(enableValidation, typeResolver); var parser = new ExpressionParser(enableValidation, typeResolver);
var node = parser.Parse(reader); var node = parser.Parse(ref reader);
if (!reader.End) if (!reader.End)
{ {

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

@ -20,7 +20,7 @@ namespace Avalonia.Markup.Parsers
_enableValidation = enableValidation; _enableValidation = enableValidation;
} }
public ExpressionNode Parse(Reader r) public ExpressionNode Parse(ref Reader r)
{ {
var nodes = new List<ExpressionNode>(); var nodes = new List<ExpressionNode>();
var state = State.Start; var state = State.Start;
@ -30,23 +30,23 @@ namespace Avalonia.Markup.Parsers
switch (state) switch (state)
{ {
case State.Start: case State.Start:
state = ParseStart(r, nodes); state = ParseStart(ref r, nodes);
break; break;
case State.AfterMember: case State.AfterMember:
state = ParseAfterMember(r, nodes); state = ParseAfterMember(ref r, nodes);
break; break;
case State.BeforeMember: case State.BeforeMember:
state = ParseBeforeMember(r, nodes); state = ParseBeforeMember(ref r, nodes);
break; break;
case State.AttachedProperty: case State.AttachedProperty:
state = ParseAttachedProperty(r, nodes); state = ParseAttachedProperty(ref r, nodes);
break; break;
case State.Indexer: case State.Indexer:
state = ParseIndexer(r, nodes); state = ParseIndexer(ref r, nodes);
break; break;
} }
} }
@ -64,18 +64,18 @@ namespace Avalonia.Markup.Parsers
return nodes.FirstOrDefault(); 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()); nodes.Add(new LogicalNotNode());
return State.Start; return State.Start;
} }
else if (ParseOpenBrace(r)) else if (ParseOpenBrace(ref r))
{ {
return State.AttachedProperty; return State.AttachedProperty;
} }
else if (PeekOpenBracket(r)) else if (PeekOpenBracket(ref r))
{ {
return State.Indexer; return State.Indexer;
} }
@ -93,18 +93,18 @@ namespace Avalonia.Markup.Parsers
return State.End; 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; return State.BeforeMember;
} }
else if (ParseStreamOperator(r)) else if (ParseStreamOperator(ref r))
{ {
nodes.Add(new StreamNode()); nodes.Add(new StreamNode());
return State.AfterMember; return State.AfterMember;
} }
else if (PeekOpenBracket(r)) else if (PeekOpenBracket(ref r))
{ {
return State.Indexer; return State.Indexer;
} }
@ -112,9 +112,9 @@ namespace Avalonia.Markup.Parsers
return State.End; 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; 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> ns = ReadOnlySpan<char>.Empty;
ReadOnlySpan<char> owner; ReadOnlySpan<char> owner;
@ -166,9 +166,9 @@ namespace Avalonia.Markup.Parsers
return State.AfterMember; 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) if (args.Count == 0)
{ {
@ -179,27 +179,27 @@ namespace Avalonia.Markup.Parsers
return State.AfterMember; return State.AfterMember;
} }
private static bool ParseNot(Reader r) private static bool ParseNot(ref Reader r)
{ {
return !r.End && r.TakeIf('!'); return !r.End && r.TakeIf('!');
} }
private static bool ParseMemberAccessor(Reader r) private static bool ParseMemberAccessor(ref Reader r)
{ {
return !r.End && r.TakeIf('.'); return !r.End && r.TakeIf('.');
} }
private static bool ParseOpenBrace(Reader r) private static bool ParseOpenBrace(ref Reader r)
{ {
return !r.End && r.TakeIf('('); return !r.End && r.TakeIf('(');
} }
private static bool PeekOpenBracket(Reader r) private static bool PeekOpenBracket(ref Reader r)
{ {
return !r.End && r.Peek == '['; return !r.End && r.Peek == '[';
} }
private static bool ParseStreamOperator(Reader r) private static bool ParseStreamOperator(ref Reader r)
{ {
return !r.End && r.TakeIf('^'); 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 namespace Avalonia.Markup.Parsers
{ {
internal class Reader internal ref struct Reader
{ {
private readonly string _s; private ReadOnlySpan<char> _s;
private int _i;
public Reader(string s) public Reader(ReadOnlySpan<char> s)
:this()
{ {
_s = s; _s = s;
} }
public bool End => _i == _s.Length; public bool End => _s.IsEmpty;
public char Peek => _s[_i]; public char Peek => _s[0];
public int Position => _i; public int Position { get; private set; }
public char Take() => _s[_i++]; public char Take()
{
Position++;
char taken = _s[0];
_s = _s.Slice(1);
return taken;
}
public void SkipWhitespace() public void SkipWhitespace()
{ {
while (!End && char.IsWhiteSpace(Peek)) var trimmed = _s.TrimStart();
{ Position += _s.Length - trimmed.Length;
Take(); _s = trimmed;
}
} }
public bool TakeIf(char c) public bool TakeIf(char c)
@ -55,53 +60,26 @@ namespace Avalonia.Markup.Parsers
public ReadOnlySpan<char> TakeUntil(char c) public ReadOnlySpan<char> TakeUntil(char c)
{ {
int startIndex = Position; int len;
while (!End && Peek != c) 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 len;
{ for (len = 0; len < _s.Length && condition(_s[len]); len++)
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;
} }
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) public static IEnumerable<ISyntax> Parse(string s)
{ {
var r = new Reader(s); var r = new Reader(s.AsSpan());
var state = State.Start; var state = State.Start;
var selector = new List<ISyntax>(); var selector = new List<ISyntax>();
while (!r.End && state != State.End) while (!r.End && state != State.End)
@ -40,34 +40,34 @@ namespace Avalonia.Markup.Parsers
switch (state) switch (state)
{ {
case State.Start: case State.Start:
(state, syntax) = ParseStart(r); state = ParseStart(ref r);
break; break;
case State.Middle: case State.Middle:
(state, syntax) = ParseMiddle(r); state = ParseMiddle(ref r);
break;
case State.CanHaveType:
state = ParseCanHaveType(ref r);
break; break;
case State.Colon: case State.Colon:
(state, syntax) = ParseColon(r); (state, syntax) = ParseColon(ref r);
break; break;
case State.Class: case State.Class:
(state, syntax) = ParseClass(r); (state, syntax) = ParseClass(ref r);
break; break;
case State.Traversal: case State.Traversal:
(state, syntax) = ParseTraversal(r); (state, syntax) = ParseTraversal(ref r);
break; break;
case State.TypeName: case State.TypeName:
(state, syntax) = ParseTypeName(r); (state, syntax) = ParseTypeName(ref r);
break;
case State.CanHaveType:
(state, syntax) = ParseCanHaveType(r);
break; break;
case State.Property: case State.Property:
(state, syntax) = ParseProperty(r); (state, syntax) = ParseProperty(ref r);
break; break;
case State.Template: case State.Template:
(state, syntax) = ParseTemplate(r); (state, syntax) = ParseTemplate(ref r);
break; break;
case State.Name: case State.Name:
(state, syntax) = ParseName(r); (state, syntax) = ParseName(ref r);
break; break;
} }
if (syntax != null) if (syntax != null)
@ -84,59 +84,64 @@ namespace Avalonia.Markup.Parsers
return selector; return selector;
} }
private static (State, ISyntax) ParseStart(Reader r) private static State ParseStart(ref Reader r)
{ {
r.SkipWhitespace(); r.SkipWhitespace();
if (r.End)
{
return State.End;
}
if (r.TakeIf(':')) if (r.TakeIf(':'))
{ {
return (State.Colon, null); return State.Colon;
} }
else if (r.TakeIf('.')) else if (r.TakeIf('.'))
{ {
return (State.Class, null); return State.Class;
} }
else if (r.TakeIf('#')) 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(':')) if (r.TakeIf(':'))
{ {
return (State.Colon, null); return State.Colon;
} }
else if (r.TakeIf('.')) else if (r.TakeIf('.'))
{ {
return (State.Class, null); return State.Class;
} }
else if (r.TakeIf(char.IsWhiteSpace) || r.Peek == '>') else if (r.TakeIf(char.IsWhiteSpace) || r.Peek == '>')
{ {
return (State.Traversal, null); return State.Traversal;
} }
else if (r.TakeIf('/')) else if (r.TakeIf('/'))
{ {
return (State.Template, null); return State.Template;
} }
else if (r.TakeIf('#')) 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('[')) 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(); var identifier = r.ParseIdentifier();
@ -145,9 +150,10 @@ namespace Avalonia.Markup.Parsers
throw new ExpressionParseException(r.Position, "Expected class name or is selector after ':'."); 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(')')) if (r.End || !r.TakeIf(')'))
{ {
throw new ExpressionParseException(r.Position, $"Expected ')', got {r.Peek}"); 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(); r.SkipWhitespace();
if (r.TakeIf('>')) 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(); var @class = r.ParseIdentifier();
if (@class.IsEmpty) if (@class.IsEmpty)
@ -199,10 +205,11 @@ namespace Avalonia.Markup.Parsers
return (State.CanHaveType, new ClassSyntax { Class = @class.ToString() }); 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(); 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()}'"); throw new ExpressionParseException(r.Position, $"Expected 'template', got '{template.ToString()}'");
} }
@ -213,7 +220,7 @@ namespace Avalonia.Markup.Parsers
return (State.Start, new TemplateSyntax()); return (State.Start, new TemplateSyntax());
} }
private static (State, ISyntax) ParseName(Reader r) private static (State, ISyntax) ParseName(ref Reader r)
{ {
var name = r.ParseIdentifier(); var name = r.ParseIdentifier();
if (name.IsEmpty) if (name.IsEmpty)
@ -223,12 +230,12 @@ namespace Avalonia.Markup.Parsers
return (State.CanHaveType, new NameSyntax { Name = name.ToString() }); 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(); var property = r.ParseIdentifier();
@ -244,8 +251,8 @@ namespace Avalonia.Markup.Parsers
return (State.CanHaveType, new PropertySyntax { Property = property.ToString(), Value = value.ToString() }); return (State.CanHaveType, new PropertySyntax { Property = property.ToString(), Value = value.ToString() });
} }
private static TSyntax ParseType<TSyntax>(Reader r) private static TSyntax ParseType<TSyntax>(ref Reader r, TSyntax syntax)
where TSyntax : ITypeSyntax, new() where TSyntax : ITypeSyntax
{ {
ReadOnlySpan<char> ns = null; ReadOnlySpan<char> ns = null;
ReadOnlySpan<char> type; ReadOnlySpan<char> type;
@ -269,11 +276,10 @@ namespace Avalonia.Markup.Parsers
{ {
type = namespaceOrTypeName; type = namespaceOrTypeName;
} }
return new TSyntax
{ syntax.Xmlns = ns.ToString();
Xmlns = ns.ToString(), syntax.TypeName = type.ToString();
TypeName = type.ToString() return syntax;
};
} }
public interface ISyntax public interface ISyntax

Loading…
Cancel
Save