Skip to content

Commit

Permalink
[.NET] Optimize AstNode.subitems with simple List and custom Enumerator
Browse files Browse the repository at this point in the history
  • Loading branch information
obligaron committed Jan 3, 2025
1 parent fca53f6 commit 47c481a
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 96 deletions.
73 changes: 33 additions & 40 deletions dotnet/Gherkin/AstBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ private object GetTransformedNode(AstNode node)
}
case RuleType.Description:
{
var lineTokens = node.GetTokens(TokenType.Other);
IEnumerable<Token> lineTokens = node.GetTokens(TokenType.Other);

// Trim trailing empty lines
lineTokens = lineTokens.Reverse().SkipWhile(t => string.IsNullOrWhiteSpace(t.MatchedText)).Reverse();
Expand All @@ -130,16 +130,16 @@ private object GetTransformedNode(AstNode node)
var children = new List<IHasLocation>();
var background = node.GetSingle<Background>(RuleType.Background);
if (background != null)
{
children.Add(background);
}
var childrenEnumerable = children.Concat(node.GetItems<IHasLocation>(RuleType.ScenarioDefinition))
.Concat(node.GetItems<IHasLocation>(RuleType.Rule));
foreach (var scenarioDefinition in node.GetItems<IHasLocation>(RuleType.ScenarioDefinition))
children.Add(scenarioDefinition);
foreach (var rule in node.GetItems<IHasLocation>(RuleType.Rule))
children.Add(rule);
var description = GetDescription(header);
if (featureLine.MatchedGherkinDialect == null) return null;
var language = featureLine.MatchedGherkinDialect.Language;

return CreateFeature(tags, GetLocation(featureLine), language, featureLine.MatchedKeyword, featureLine.MatchedText, description, childrenEnumerable.ToArray(), node);
return CreateFeature(tags, GetLocation(featureLine), language, featureLine.MatchedKeyword, featureLine.MatchedText, description, children.ToArray(), node);
}
case RuleType.Rule:
{
Expand All @@ -151,14 +151,13 @@ private object GetTransformedNode(AstNode node)
var children = new List<IHasLocation>();
var background = node.GetSingle<Background>(RuleType.Background);
if (background != null)
{
children.Add(background);
}
var childrenEnumerable = children.Concat(node.GetItems<IHasLocation>(RuleType.ScenarioDefinition));
foreach (var scenarioDefinition in node.GetItems<IHasLocation>(RuleType.ScenarioDefinition))
children.Add(scenarioDefinition);
var description = GetDescription(header);
if (ruleLine.MatchedGherkinDialect == null) return null;

return CreateRule(tags, GetLocation(ruleLine), ruleLine.MatchedKeyword, ruleLine.MatchedText, description, childrenEnumerable.ToArray(), node);
return CreateRule(tags, GetLocation(ruleLine), ruleLine.MatchedKeyword, ruleLine.MatchedText, description, children.ToArray(), node);
}
case RuleType.GherkinDocument:
{
Expand Down Expand Up @@ -260,51 +259,45 @@ private Tag[] GetTags(AstNode node)
if (tagsNode == null)
return [];

return tagsNode.GetTokens(TokenType.TagLine)
.SelectMany(t => t.MatchedItems, (t, tagItem) =>
CreateTag(GetLocation(t, tagItem.Column), tagItem.Text, tagsNode))
.ToArray();
var tags = new List<Tag>();
foreach (var line in tagsNode.GetTokens(TokenType.TagLine))
{
foreach (var matchedItem in line.MatchedItems)
tags.Add(CreateTag(GetLocation(line, matchedItem.Column), matchedItem.Text, tagsNode));
}
return tags.ToArray();
}

private TableRow[] GetTableRows(AstNode node)
{
var rows = node.GetTokens(TokenType.TableRow).Select(token => CreateTableRow(GetLocation(token), GetCells(token), node)).ToArray();
CheckCellCountConsistency(rows);
return rows;
}

protected virtual void CheckCellCountConsistency(TableRow[] rows)
{
if (rows.Length == 0)
return;

int cellCount = rows[0].Cells.Count();
for (int i = 1; i < rows.Length; i++)
var rows = new List<TableRow>();
int cellCount = 0;
bool firstRow = true;
foreach (var rowToken in node.GetTokens(TokenType.TableRow))
{
var row = rows[i];
if (row.Cells.Count() != cellCount)
var rowLocation = GetLocation(rowToken);
var cells = new List<TableCell>();
foreach (var cellItem in rowToken.MatchedItems)
cells.Add(CreateTableCell(GetLocation(rowToken, cellItem.Column), cellItem.Text));
if (firstRow)
{
HandleAstError("inconsistent cell count within the table", row.Location);
cellCount = cells.Count;
firstRow = false;
}
else if (cells.Count != cellCount)
{
HandleAstError("inconsistent cell count within the table", rowLocation);
}
rows.Add(CreateTableRow(rowLocation, cells, node));
}
return rows.ToArray();
}

protected virtual void HandleAstError(string message, Location location)
{
throw new AstBuilderException(message, location);
}

private TableCell[] GetCells(Token tableRowToken)
{
var cells = new TableCell[tableRowToken.MatchedItems.Length];
for (int i = 0; i < cells.Length; i++)
{
var cellItem = tableRowToken.MatchedItems[i];
cells[i] = CreateTableCell(GetLocation(tableRowToken, cellItem.Column), cellItem.Text);
}
return cells;
}

private static Step[] GetSteps(AstNode scenarioDefinitionNode)
{
return scenarioDefinitionNode.GetItems<Step>(RuleType.Step).ToArray();
Expand Down
120 changes: 64 additions & 56 deletions dotnet/Gherkin/AstNode.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using System.Collections;
using System.Diagnostics;

namespace Gherkin;

[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
public class AstNode(RuleType ruleType)
{
private readonly Dictionary<RuleType, object> subItems = new Dictionary<RuleType, object>();
private readonly List<(RuleType RuleType, object Item)> subItems = new(4);

public RuleType RuleType { get; } = ruleType;

Expand All @@ -11,86 +15,90 @@ public Token GetToken(TokenType tokenType)
return GetSingle<Token>((RuleType)tokenType);
}

public IEnumerable<Token> GetTokens(TokenType tokenType)
public Enumerable<Token> GetTokens(TokenType tokenType)
{
return GetItems<Token>((RuleType)tokenType);
}

public T GetSingle<T>(RuleType ruleType)
{
if (!subItems.TryGetValue(ruleType, out var items))
return default;
if (items is List<object> list)
bool foundOne = false;
T ret = default;
foreach ((var itemType, var item) in subItems)
{
T ret = default;
bool foundOne = false;
foreach (var item in list)
{
if (item is T tItem)
{
if (foundOne)
throw new InvalidOperationException();
ret = tItem;
foundOne = true;
}
}
if (itemType != ruleType)
continue;
if (item is not T tItem)
continue;
if (foundOne)
return ret;
else
throw new InvalidOperationException();
ret = tItem;
foundOne = true;
}
else if (items is T tItem)
{
return tItem;
}
return default;
return ret;
}

public IEnumerable<T> GetItems<T>(RuleType ruleType)
public readonly struct Enumerable<T> : IEnumerable<T>
{
if (!subItems.TryGetValue(ruleType, out var items))
yield break;
if (items is List<object> list)
{
foreach (var item in list)
{
if (item is T tItem)
yield return tItem;
}
}
else if (items is T tItem)
readonly List<(RuleType RuleType, object Item)> subItems;
readonly RuleType ruleType;

internal Enumerable(List<(RuleType RuleType, object Item)> subItems, RuleType ruleType)
{
yield return tItem;
this.subItems = subItems;
this.ruleType = ruleType;
}
}

public void SetSingle<T>(RuleType ruleType, T value)
{
subItems[ruleType] = new object[] { value };
}
public Enumerator<T> GetEnumerator() => new Enumerator<T>(subItems.GetEnumerator(), ruleType);

public void AddRange<T>(RuleType ruleType, IEnumerable<T> values)
{
foreach (var value in values)
{
Add(ruleType, value);
}
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public void Add<T>(RuleType ruleType, T obj)
public struct Enumerator<T> : IEnumerator<T>
{
if (!subItems.TryGetValue(ruleType, out var items))
private List<(RuleType RuleType, object Item)>.Enumerator enumerator;
readonly RuleType ruleType;

internal Enumerator(List<(RuleType RuleType, object Item)>.Enumerator enumerator, RuleType ruleType)
{
subItems.Add(ruleType, obj);
this.enumerator = enumerator;
this.ruleType = ruleType;
}
else if (items is List<object> list)

public T Current { readonly get; private set; }
readonly object IEnumerator.Current => Current;

public readonly void Dispose()
{
list.Add(obj);
// nothing to do
}
else

public bool MoveNext()
{
list = [items, obj];
subItems[ruleType] = list;
while (enumerator.MoveNext())
{
(var itemType, var item) = enumerator.Current;
if (itemType != ruleType)
continue;
if (item is not T tItem)
continue;
Current = tItem;
return true;
}
Current = default;
return false;
}

public void Reset() => throw new NotImplementedException();
}

public Enumerable<T> GetItems<T>(RuleType ruleType) => new Enumerable<T>(subItems, ruleType);

public void Add<T>(RuleType ruleType, T obj)
{
subItems.Add((ruleType, obj));
}

string GetDebuggerDisplay() => $"RuleType: {RuleType} with item count: {(subItems.Count == 0 ? "<none>" : string.Join(", ", subItems.GroupBy(x => x.RuleType).Select(x => $"{x.Key}:{x.Count()}")))}";
}

0 comments on commit 47c481a

Please sign in to comment.