From 47c481a22e585feef7a47582418d5eac843cc324 Mon Sep 17 00:00:00 2001 From: obligaron Date: Sun, 29 Dec 2024 15:07:23 +0100 Subject: [PATCH] [.NET] Optimize AstNode.subitems with simple List and custom Enumerator --- dotnet/Gherkin/AstBuilder.cs | 73 ++++++++++----------- dotnet/Gherkin/AstNode.cs | 120 +++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 96 deletions(-) diff --git a/dotnet/Gherkin/AstBuilder.cs b/dotnet/Gherkin/AstBuilder.cs index 7fc411d4a..12ca3927c 100644 --- a/dotnet/Gherkin/AstBuilder.cs +++ b/dotnet/Gherkin/AstBuilder.cs @@ -113,7 +113,7 @@ private object GetTransformedNode(AstNode node) } case RuleType.Description: { - var lineTokens = node.GetTokens(TokenType.Other); + IEnumerable lineTokens = node.GetTokens(TokenType.Other); // Trim trailing empty lines lineTokens = lineTokens.Reverse().SkipWhile(t => string.IsNullOrWhiteSpace(t.MatchedText)).Reverse(); @@ -130,16 +130,16 @@ private object GetTransformedNode(AstNode node) var children = new List(); var background = node.GetSingle(RuleType.Background); if (background != null) - { children.Add(background); - } - var childrenEnumerable = children.Concat(node.GetItems(RuleType.ScenarioDefinition)) - .Concat(node.GetItems(RuleType.Rule)); + foreach (var scenarioDefinition in node.GetItems(RuleType.ScenarioDefinition)) + children.Add(scenarioDefinition); + foreach (var rule in node.GetItems(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: { @@ -151,14 +151,13 @@ private object GetTransformedNode(AstNode node) var children = new List(); var background = node.GetSingle(RuleType.Background); if (background != null) - { children.Add(background); - } - var childrenEnumerable = children.Concat(node.GetItems(RuleType.ScenarioDefinition)); + foreach (var scenarioDefinition in node.GetItems(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: { @@ -260,33 +259,38 @@ 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(); + 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(); + 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(); + 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) @@ -294,17 +298,6 @@ 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(RuleType.Step).ToArray(); diff --git a/dotnet/Gherkin/AstNode.cs b/dotnet/Gherkin/AstNode.cs index 8c0fbc108..3afe6895b 100644 --- a/dotnet/Gherkin/AstNode.cs +++ b/dotnet/Gherkin/AstNode.cs @@ -1,8 +1,12 @@ +using System.Collections; +using System.Diagnostics; + namespace Gherkin; +[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] public class AstNode(RuleType ruleType) { - private readonly Dictionary subItems = new Dictionary(); + private readonly List<(RuleType RuleType, object Item)> subItems = new(4); public RuleType RuleType { get; } = ruleType; @@ -11,86 +15,90 @@ public Token GetToken(TokenType tokenType) return GetSingle((RuleType)tokenType); } - public IEnumerable GetTokens(TokenType tokenType) + public Enumerable GetTokens(TokenType tokenType) { return GetItems((RuleType)tokenType); } public T GetSingle(RuleType ruleType) { - if (!subItems.TryGetValue(ruleType, out var items)) - return default; - if (items is List 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 GetItems(RuleType ruleType) + public readonly struct Enumerable : IEnumerable { - if (!subItems.TryGetValue(ruleType, out var items)) - yield break; - if (items is List 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(RuleType ruleType, T value) - { - subItems[ruleType] = new object[] { value }; - } + public Enumerator GetEnumerator() => new Enumerator(subItems.GetEnumerator(), ruleType); - public void AddRange(RuleType ruleType, IEnumerable values) - { - foreach (var value in values) - { - Add(ruleType, value); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - public void Add(RuleType ruleType, T obj) + public struct Enumerator : IEnumerator { - 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 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 GetItems(RuleType ruleType) => new Enumerable(subItems, ruleType); + + public void Add(RuleType ruleType, T obj) + { + subItems.Add((ruleType, obj)); + } + + string GetDebuggerDisplay() => $"RuleType: {RuleType} with item count: {(subItems.Count == 0 ? "" : string.Join(", ", subItems.GroupBy(x => x.RuleType).Select(x => $"{x.Key}:{x.Count()}")))}"; }