From c10707b271629793c37b83523f8338ab9defb795 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Sun, 30 Mar 2025 14:37:51 +0500 Subject: [PATCH] Speedup merging of parse error --- Sources/SynKit/Libraries/Parser/ParseError.cs | 26 ++--- .../Parser/ParseErrorElementDictionary.cs | 94 +++++++++++++++++++ 2 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 Sources/SynKit/Libraries/Parser/ParseErrorElementDictionary.cs diff --git a/Sources/SynKit/Libraries/Parser/ParseError.cs b/Sources/SynKit/Libraries/Parser/ParseError.cs index 78f6e0c0..5dcd99e0 100644 --- a/Sources/SynKit/Libraries/Parser/ParseError.cs +++ b/Sources/SynKit/Libraries/Parser/ParseError.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; -using System.Collections.Generic.Polyfill; -using System.Linq; namespace Yoakke.SynKit.Parser; @@ -14,10 +12,12 @@ namespace Yoakke.SynKit.Parser; /// public class ParseError { + private ParseErrorElementDictionary elements; + /// /// The error cases in different parse contexts. /// - public IReadOnlyDictionary Elements { get; } + public IReadOnlyDictionary Elements => this.elements; /// /// The item that was found, if any. @@ -37,13 +37,13 @@ public class ParseError /// The position where the error occurred. /// The context in which the error occurred. public ParseError(object expected, object? got, IComparable position, string context) - : this(new Dictionary { { context, new ParseErrorElement(expected, context) } }, got, position) + : this(new ParseErrorElementDictionary(context, new ParseErrorElement(expected, context)), got, position) { } - private ParseError(IReadOnlyDictionary elements, object? got, IComparable position) + private ParseError(ParseErrorElementDictionary elements, object? got, IComparable position) { - this.Elements = elements; + this.elements = elements; this.Got = got; this.Position = position; } @@ -65,19 +65,7 @@ private ParseError(IReadOnlyDictionary elements, obje if (cmp < 0) return second; if (cmp > 0) return first; // Both of them got stuck at the same place, merge entries - var elements = first.Elements.Values.ToDictionary(e => e.Context, e => new ParseErrorElement(e.Expected.ToHashSet(), e.Context)); - foreach (var element in second.Elements.Values) - { - if (elements.TryGetValue(element.Context, out var part)) - { - foreach (var e in element.Expected) part.Expected.Add(e); - } - else - { - part = new (element.Expected.ToHashSet(), element.Context); - elements.Add(element.Context, part); - } - } + var elements = first.elements.Merge(second.elements); // TODO: Think this through // NOTE: Could it ever happen that first.Got and second.Got are different but neither are null? // Would we want to unify these and move them to ParseErrorElement or something? diff --git a/Sources/SynKit/Libraries/Parser/ParseErrorElementDictionary.cs b/Sources/SynKit/Libraries/Parser/ParseErrorElementDictionary.cs new file mode 100644 index 00000000..c908badb --- /dev/null +++ b/Sources/SynKit/Libraries/Parser/ParseErrorElementDictionary.cs @@ -0,0 +1,94 @@ +// Copyright (c) 2021-2022 Yoakke. +// Licensed under the Apache License, Version 2.0. +// Source repository: https://github.com/LanguageDev/Yoakke + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Generic.Polyfill; +using System.Linq; + +namespace Yoakke.SynKit.Parser; + +internal class ParseErrorElementDictionary : IReadOnlyDictionary +{ + private string? firstKey; + private ParseErrorElement? firstItem; + private Dictionary? elements; + + private ParseErrorElementDictionary() + { + } + + private ParseErrorElementDictionary(Dictionary elements) + { + this.elements = elements; + } + + public ParseErrorElementDictionary(string key, ParseErrorElement value) + { + this.firstKey = key; + this.firstItem = value; + } + + public ParseErrorElement this[string key] => this.firstKey is null + ? this.elements is null + ? throw new KeyNotFoundException($"The key {key} was not found in the dictionary") + : this.elements[key] + : this.firstItem!; + + public IEnumerable Keys => this.firstKey is null ? this.elements!.Keys : new[] { this.firstKey }; + + public IEnumerable Values => this.firstKey is null ? this.elements!.Values : new[] { this.firstItem! }; + + public int Count => this.firstKey is null ? this.elements is null ? 0 : this.elements.Count : 1; + + public bool ContainsKey(string key) => this.firstKey is null ? this.elements is null ? false : this.elements.ContainsKey(key) : this.firstKey == key; + public IEnumerator> GetEnumerator() => throw new NotImplementedException(); + public bool TryGetValue(string key, out ParseErrorElement value) => throw new NotImplementedException(); + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + public ParseErrorElementDictionary Merge(ParseErrorElementDictionary other) + { + if (this.elements is null) + { + if (other.elements is null) + { + if (this.firstKey == other.firstKey) + { + var newExpected = other.firstItem!.Expected.ToHashSet(); + newExpected.UnionWith(this.firstItem!.Expected); + return new(other.firstKey!, new (newExpected, this.firstItem.Context)); + } + else + { + return new(new Dictionary + { + { this.firstKey!, this.firstItem! }, + { other.firstKey!, other.firstItem! }, + }); + } + } + else + { + return new(new (other.elements)); + } + } + + var elements = this.elements.Values.ToDictionary(e => e.Context, e => new ParseErrorElement(e.Expected.ToHashSet(), e.Context)); + foreach (var element in other.Values) + { + if (elements.TryGetValue(element.Context, out var part)) + { + foreach (var e in element.Expected) part.Expected.Add(e); + } + else + { + part = new(element.Expected.ToHashSet(), element.Context); + elements.Add(element.Context, part); + } + } + + return new (elements); + } +}