From 2a14a3a3ed9b50360d6e072ced45b910a64c7b98 Mon Sep 17 00:00:00 2001 From: majocha Date: Mon, 13 Feb 2023 00:25:13 +0100 Subject: [PATCH 1/4] remove PatternMatcher project --- VisualFSharp.sln | 15 - .../src/FSharp.Editor/FSharp.Editor.fsproj | 1 - .../Navigation/NavigateToSearchService.fs | 21 +- .../ArrayBuilder.Enumerator.cs | 55 -- .../src/FSharp.PatternMatcher/ArrayBuilder.cs | 530 ------------- .../src/FSharp.PatternMatcher/ArraySlice.cs | 81 -- .../FSharp.PatternMatcher/BKTree.Builder.cs | 293 ------- .../src/FSharp.PatternMatcher/BKTree.Edge.cs | 36 - .../src/FSharp.PatternMatcher/BKTree.Node.cs | 48 -- .../src/FSharp.PatternMatcher/BKTree.cs | 220 ------ .../src/FSharp.PatternMatcher/EditDistance.cs | 677 ---------------- .../FSharp.PatternMatcher.csproj | 24 - .../src/FSharp.PatternMatcher/Hash.cs | 366 --------- .../IDictionaryExtensions.cs | 23 - .../FSharp.PatternMatcher/IObjectWritable.cs | 10 - .../ImmutableArrayExtensions.cs | 14 - .../NormalizedTextSpanCollection.cs | 630 --------------- .../src/FSharp.PatternMatcher/ObjectPool.cs | 279 ------- .../src/FSharp.PatternMatcher/ObjectReader.cs | 24 - .../src/FSharp.PatternMatcher/ObjectWriter.cs | 24 - .../src/FSharp.PatternMatcher/PatternMatch.cs | 127 --- .../FSharp.PatternMatcher/PatternMatchKind.cs | 36 - .../PatternMatcher.Segment.cs | 121 --- .../PatternMatcher.TextChunk.cs | 45 -- .../FSharp.PatternMatcher/PatternMatcher.cs | 737 ------------------ .../FSharp.PatternMatcher/PatternMatches.cs | 42 - .../FSharp.PatternMatcher/PooledHashSet.cs | 44 -- .../PooledStringBuilder.cs | 65 -- .../Properties/AssemblyInfo.cs | 9 - .../src/FSharp.PatternMatcher/SpellChecker.cs | 252 ------ .../FSharp.PatternMatcher/StringBreaker.cs | 326 -------- .../src/FSharp.PatternMatcher/StringSlice.cs | 196 ----- .../src/FSharp.PatternMatcher/TextSpan.cs | 253 ------ 33 files changed, 9 insertions(+), 5615 deletions(-) delete mode 100644 vsintegration/src/FSharp.PatternMatcher/ArrayBuilder.Enumerator.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/ArrayBuilder.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/ArraySlice.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/BKTree.Builder.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/BKTree.Edge.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/BKTree.Node.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/BKTree.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/EditDistance.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/FSharp.PatternMatcher.csproj delete mode 100644 vsintegration/src/FSharp.PatternMatcher/Hash.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/IDictionaryExtensions.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/IObjectWritable.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/ImmutableArrayExtensions.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/NormalizedTextSpanCollection.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/ObjectPool.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/ObjectReader.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/ObjectWriter.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/PatternMatch.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/PatternMatchKind.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/PatternMatcher.Segment.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/PatternMatcher.TextChunk.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/PatternMatcher.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/PatternMatches.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/PooledHashSet.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/PooledStringBuilder.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/Properties/AssemblyInfo.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/SpellChecker.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/StringBreaker.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/StringSlice.cs delete mode 100644 vsintegration/src/FSharp.PatternMatcher/TextSpan.cs diff --git a/VisualFSharp.sln b/VisualFSharp.sln index 821fb3e510a..71e87c0f5b1 100644 --- a/VisualFSharp.sln +++ b/VisualFSharp.sln @@ -119,8 +119,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceFile", "vsintegrati EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Build.UnitTests", "tests\FSharp.Build.UnitTests\FSharp.Build.UnitTests.fsproj", "{400FAB03-786E-40CC-85A8-04B0C2869B14}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.PatternMatcher", "vsintegration\src\FSharp.PatternMatcher\FSharp.PatternMatcher.csproj", "{18227628-DF90-4C47-AF3D-CC72D2EDD986}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Setup", "Setup", "{6235B3AF-774D-4EA1-8F37-789E767F6368}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FSharp.Compiler.MSBuild", "setup\Swix\Microsoft.FSharp.Compiler.MSBuild\Microsoft.FSharp.Compiler.MSBuild.csproj", "{4CBEE353-EB7F-4A47-988B-0070AEB4EE7A}" @@ -697,18 +695,6 @@ Global {400FAB03-786E-40CC-85A8-04B0C2869B14}.Release|Any CPU.Build.0 = Release|Any CPU {400FAB03-786E-40CC-85A8-04B0C2869B14}.Release|x86.ActiveCfg = Release|Any CPU {400FAB03-786E-40CC-85A8-04B0C2869B14}.Release|x86.Build.0 = Release|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Debug|Any CPU.Build.0 = Debug|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Debug|x86.ActiveCfg = Debug|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Debug|x86.Build.0 = Debug|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Proto|Any CPU.ActiveCfg = Release|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Proto|Any CPU.Build.0 = Release|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Proto|x86.ActiveCfg = Release|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Proto|x86.Build.0 = Release|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Release|Any CPU.ActiveCfg = Release|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Release|Any CPU.Build.0 = Release|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Release|x86.ActiveCfg = Release|Any CPU - {18227628-DF90-4C47-AF3D-CC72D2EDD986}.Release|x86.Build.0 = Release|Any CPU {4CBEE353-EB7F-4A47-988B-0070AEB4EE7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4CBEE353-EB7F-4A47-988B-0070AEB4EE7A}.Debug|Any CPU.Build.0 = Debug|Any CPU {4CBEE353-EB7F-4A47-988B-0070AEB4EE7A}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -1084,7 +1070,6 @@ Global {FF76BD3C-5E0A-4752-B6C3-044F6E15719B} = {35636A82-401A-4C3A-B2AB-EB7DC5E9C268} {0385564F-07B4-4264-AB8A-17C393E9140C} = {F6DAEE9A-8BE1-4C4A-BC83-09215517C7DA} {400FAB03-786E-40CC-85A8-04B0C2869B14} = {CFE3259A-2D30-4EB0-80D5-E8B5F3D01449} - {18227628-DF90-4C47-AF3D-CC72D2EDD986} = {4C7B48D7-19AF-4AE7-9D1D-3BB289D5480D} {4CBEE353-EB7F-4A47-988B-0070AEB4EE7A} = {6235B3AF-774D-4EA1-8F37-789E767F6368} {6BCFED7A-3F67-4180-B307-C7D69D191D8C} = {6235B3AF-774D-4EA1-8F37-789E767F6368} {E93E7D28-1C6B-4E04-BE83-68428CF7E039} = {6235B3AF-774D-4EA1-8F37-789E767F6368} diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 1ad4cab4f5f..4e83825b9b8 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -146,7 +146,6 @@ - diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index b1dfdb2a684..fd720c56035 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -13,11 +13,10 @@ open System.Globalization open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text -open Microsoft.CodeAnalysis.PatternMatching open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation open Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo +open Microsoft.VisualStudio.Text.PatternMatching -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.EditorServices open FSharp.Compiler.Syntax @@ -172,6 +171,7 @@ module private Utils = type internal FSharpNavigateToSearchService [] ( + patternMatcherFactory: IPatternMatcherFactory ) = let kindsProvided = ImmutableHashSet.Create(FSharpNavigateToItemKind.Module, FSharpNavigateToItemKind.Class, FSharpNavigateToItemKind.Field, FSharpNavigateToItemKind.Property, FSharpNavigateToItemKind.Method, FSharpNavigateToItemKind.Enum, FSharpNavigateToItemKind.EnumItem) :> IImmutableSet @@ -229,8 +229,6 @@ type internal FSharpNavigateToSearchService | PatternMatchKind.Exact -> FSharpNavigateToMatchKind.Exact | PatternMatchKind.Prefix -> FSharpNavigateToMatchKind.Prefix | PatternMatchKind.Substring -> FSharpNavigateToMatchKind.Substring - | PatternMatchKind.CamelCase -> FSharpNavigateToMatchKind.Regular - | PatternMatchKind.Fuzzy -> FSharpNavigateToMatchKind.Regular | _ -> FSharpNavigateToMatchKind.Regular interface IFSharpNavigateToSearchService with @@ -250,14 +248,13 @@ type internal FSharpNavigateToSearchService |> Array.filter (fun x -> x.Name.Length = 1 && String.Equals(x.Name, searchPattern, StringComparison.InvariantCultureIgnoreCase)) else [| yield! items |> Array.map (fun items -> items.Find(searchPattern)) |> Array.concat - use patternMatcher = new PatternMatcher(searchPattern, allowFuzzyMatching = true) - yield! items - |> Array.collect (fun item -> item.AllItems) - |> Array.Parallel.collect (fun x -> - patternMatcher.GetMatches(x.LogicalName) - |> Seq.map (fun pm -> - NavigateToSearchResult(x, patternMatchKindToNavigateToMatchKind pm.Kind) :> FSharpNavigateToSearchResult) - |> Seq.toArray) |] + let patternMatcherOptions = PatternMatcherCreationOptions(cultureInfo = CultureInfo.CurrentUICulture, flags = PatternMatcherCreationFlags.AllowFuzzyMatching) + let patternMatcher = patternMatcherFactory.CreatePatternMatcher(searchPattern, patternMatcherOptions) + for item in items |> Array.collect (fun item -> item.AllItems) do + match patternMatcher.TryMatch(item.LogicalName) |> Option.ofNullable with + | Some pm -> yield NavigateToSearchResult(item, patternMatchKindToNavigateToMatchKind pm.Kind) :> FSharpNavigateToSearchResult + | _ -> () + |] return items |> Array.distinctBy (fun x -> x.NavigableItem.Document.Id, x.NavigableItem.SourceSpan) } diff --git a/vsintegration/src/FSharp.PatternMatcher/ArrayBuilder.Enumerator.cs b/vsintegration/src/FSharp.PatternMatcher/ArrayBuilder.Enumerator.cs deleted file mode 100644 index 95da8cf50fd..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/ArrayBuilder.Enumerator.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.CodeAnalysis -{ - internal partial class ArrayBuilder - { - /// - /// struct enumerator used in foreach. - /// - internal struct Enumerator : IEnumerator - { - private readonly ArrayBuilder _builder; - private int _index; - - public Enumerator(ArrayBuilder builder) - { - _builder = builder; - _index = -1; - } - - public T Current - { - get - { - return _builder[_index]; - } - } - - public bool MoveNext() - { - _index++; - return _index < _builder.Count; - } - - public void Dispose() - { - } - - object System.Collections.IEnumerator.Current - { - get - { - return this.Current; - } - } - - public void Reset() - { - _index = -1; - } - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/ArrayBuilder.cs b/vsintegration/src/FSharp.PatternMatcher/ArrayBuilder.cs deleted file mode 100644 index da84ae39dd0..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/ArrayBuilder.cs +++ /dev/null @@ -1,530 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using Roslyn.Utilities; -using Microsoft.CodeAnalysis.Collections; - -namespace Microsoft.CodeAnalysis -{ - [DebuggerDisplay("Count = {Count,nq}")] - [DebuggerTypeProxy(typeof(ArrayBuilder<>.DebuggerProxy))] - internal sealed partial class ArrayBuilder : IReadOnlyCollection, IReadOnlyList - { - #region DebuggerProxy - - private sealed class DebuggerProxy - { - private readonly ArrayBuilder _builder; - - public DebuggerProxy(ArrayBuilder builder) - { - _builder = builder; - } - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public T[] A - { - get - { - var result = new T[_builder.Count]; - for (int i = 0; i < result.Length; i++) - { - result[i] = _builder[i]; - } - - return result; - } - } - } - - #endregion - - private readonly ImmutableArray.Builder _builder; - - private readonly ObjectPool> _pool; - - public ArrayBuilder(int size) - { - _builder = ImmutableArray.CreateBuilder(size); - } - - public ArrayBuilder() : - this(8) - { } - - private ArrayBuilder(ObjectPool> pool) : - this() - { - _pool = pool; - } - - /// - /// Realizes the array. - /// - public ImmutableArray ToImmutable() - { - return _builder.ToImmutable(); - } - - public int Count - { - get - { - return _builder.Count; - } - set - { - _builder.Count = value; - } - } - - public T this[int index] - { - get - { - return _builder[index]; - } - - set - { - _builder[index] = value; - } - } - - /// - /// Write to slot . - /// Fills in unallocated slots preceding the , if any. - /// - public void SetItem(int index, T value) - { - while (index > _builder.Count) - { - _builder.Add(default(T)); - } - - if (index == _builder.Count) - { - _builder.Add(value); - } - else - { - _builder[index] = value; - } - } - - public void Add(T item) - { - _builder.Add(item); - } - - public void Insert(int index, T item) - { - _builder.Insert(index, item); - } - - public void EnsureCapacity(int capacity) - { - if (_builder.Capacity < capacity) - { - _builder.Capacity = capacity; - } - } - - public void Clear() - { - _builder.Clear(); - } - - public bool Contains(T item) - { - return _builder.Contains(item); - } - - public int IndexOf(T item) - { - return _builder.IndexOf(item); - } - - public int IndexOf(T item, IEqualityComparer equalityComparer) - { - return _builder.IndexOf(item, 0, _builder.Count, equalityComparer); - } - - public int IndexOf(T item, int startIndex, int count) - { - return _builder.IndexOf(item, startIndex, count); - } - - public int FindIndex(Predicate match) - => FindIndex(0, this.Count, match); - - public int FindIndex(int startIndex, Predicate match) - => FindIndex(startIndex, this.Count - startIndex, match); - - public int FindIndex(int startIndex, int count, Predicate match) - { - int endIndex = startIndex + count; - for (int i = startIndex; i < endIndex; i++) - { - if (match(_builder[i])) - { - return i; - } - } - - return -1; - } - - public void RemoveAt(int index) - { - _builder.RemoveAt(index); - } - - public void RemoveLast() - { - _builder.RemoveAt(_builder.Count - 1); - } - - public void ReverseContents() - { - _builder.Reverse(); - } - - public void Sort() - { - _builder.Sort(); - } - - public void Sort(IComparer comparer) - { - _builder.Sort(comparer); - } - - public void Sort(Comparison compare) - => Sort(Comparer.Create(compare)); - - public void Sort(int startIndex, IComparer comparer) - { - _builder.Sort(startIndex, _builder.Count - startIndex, comparer); - } - - public T[] ToArray() - { - return _builder.ToArray(); - } - - public void CopyTo(T[] array, int start) - { - _builder.CopyTo(array, start); - } - - public T Last() - { - return _builder[_builder.Count - 1]; - } - - public T First() - { - return _builder[0]; - } - - public bool Any() - { - return _builder.Count > 0; - } - - /// - /// Realizes the array. - /// - public ImmutableArray ToImmutableOrNull() - { - if (Count == 0) - { - return default(ImmutableArray); - } - - return this.ToImmutable(); - } - - /// - /// Realizes the array, downcasting each element to a derived type. - /// - public ImmutableArray ToDowncastedImmutable() - where U : T - { - if (Count == 0) - { - return ImmutableArray.Empty; - } - - var tmp = ArrayBuilder.GetInstance(Count); - foreach (var i in this) - { - tmp.Add((U)i); - } - - return tmp.ToImmutableAndFree(); - } - - /// - /// Realizes the array and disposes the builder in one operation. - /// - public ImmutableArray ToImmutableAndFree() - { - var result = this.ToImmutable(); - this.Free(); - return result; - } - - public T[] ToArrayAndFree() - { - var result = this.ToArray(); - this.Free(); - return result; - } - - #region Poolable - - // To implement Poolable, you need two things: - // 1) Expose Freeing primitive. - public void Free() - { - var pool = _pool; - if (pool != null) - { - // According to the statistics of a C# compiler self-build, the most commonly used builder size is 0. (808003 uses). - // The distant second is the Count == 1 (455619), then 2 (106362) ... - // After about 50 (just 67) we have a long tail of infrequently used builder sizes. - // However we have builders with size up to 50K (just one such thing) - // - // We do not want to retain (potentially indefinitely) very large builders - // while the chance that we will need their size is diminishingly small. - // It makes sense to constrain the size to some "not too small" number. - // Overall perf does not seem to be very sensitive to this number, so I picked 128 as a limit. - if (this.Count < 128) - { - if (this.Count != 0) - { - this.Clear(); - } - - pool.Free(this); - return; - } - else - { - pool.ForgetTrackedObject(this); - } - } - } - - // 2) Expose the pool or the way to create a pool or the way to get an instance. - // for now we will expose both and figure which way works better - private static readonly ObjectPool> s_poolInstance = CreatePool(); - public static ArrayBuilder GetInstance() - { - var builder = s_poolInstance.Allocate(); - Debug.Assert(builder.Count == 0); - return builder; - } - - public static ArrayBuilder GetInstance(int capacity) - { - var builder = GetInstance(); - builder.EnsureCapacity(capacity); - return builder; - } - - public static ArrayBuilder GetInstance(int capacity, T fillWithValue) - { - var builder = GetInstance(); - builder.EnsureCapacity(capacity); - - for (int i = 0; i < capacity; i++) - { - builder.Add(fillWithValue); - } - - return builder; - } - - public static ObjectPool> CreatePool() - { - return CreatePool(128); // we rarely need more than 10 - } - - public static ObjectPool> CreatePool(int size) - { - ObjectPool> pool = null; - pool = new ObjectPool>(() => new ArrayBuilder(pool), size); - return pool; - } - - #endregion - - public Enumerator GetEnumerator() - { - return new Enumerator(this); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - internal Dictionary> ToDictionary(Func keySelector, IEqualityComparer comparer = null) - { - if (this.Count == 1) - { - var dictionary1 = new Dictionary>(1, comparer); - T value = this[0]; - dictionary1.Add(keySelector(value), ImmutableArray.Create(value)); - return dictionary1; - } - - if (this.Count == 0) - { - return new Dictionary>(comparer); - } - - // bucketize - // prevent reallocation. it may not have 'count' entries, but it won't have more. - var accumulator = new Dictionary>(Count, comparer); - for (int i = 0; i < Count; i++) - { - var item = this[i]; - var key = keySelector(item); - ArrayBuilder bucket; - if (!accumulator.TryGetValue(key, out bucket)) - { - bucket = ArrayBuilder.GetInstance(); - accumulator.Add(key, bucket); - } - - bucket.Add(item); - } - - var dictionary = new Dictionary>(accumulator.Count, comparer); - - // freeze - foreach (var pair in accumulator) - { - dictionary.Add(pair.Key, pair.Value.ToImmutableAndFree()); - } - - return dictionary; - } - - public void AddRange(ArrayBuilder items) - { - _builder.AddRange(items._builder); - } - - public void AddRange(ArrayBuilder items) where U : T - { - _builder.AddRange(items._builder); - } - - public void AddRange(ImmutableArray items) - { - _builder.AddRange(items); - } - - public void AddRange(ImmutableArray items, int length) - { - _builder.AddRange(items, length); - } - - public void AddRange(ImmutableArray items) where S : class, T - { - AddRange(ImmutableArray.CastUp(items)); - } - - public void AddRange(T[] items, int start, int length) - { - for (int i = start, end = start + length; i < end; i++) - { - Add(items[i]); - } - } - - public void AddRange(IEnumerable items) - { - _builder.AddRange(items); - } - - public void AddRange(params T[] items) - { - _builder.AddRange(items); - } - - public void AddRange(T[] items, int length) - { - _builder.AddRange(items, length); - } - - public void Clip(int limit) - { - Debug.Assert(limit <= Count); - _builder.Count = limit; - } - - public void ZeroInit(int count) - { - _builder.Clear(); - _builder.Count = count; - } - - public void AddMany(T item, int count) - { - for (int i = 0; i < count; i++) - { - Add(item); - } - } - - public void RemoveDuplicates() - { - var set = PooledHashSet.GetInstance(); - - int j = 0; - for (int i = 0; i < Count; i++) - { - if (set.Add(this[i])) - { - this[j] = this[i]; - j++; - } - } - - Clip(j); - set.Free(); - } - - public ImmutableArray SelectDistinct(Func selector) - { - var result = ArrayBuilder.GetInstance(Count); - var set = PooledHashSet.GetInstance(); - - foreach (var item in this) - { - var selected = selector(item); - if (set.Add(selected)) - { - result.Add(selected); - } - } - - set.Free(); - return result.ToImmutableAndFree(); - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/ArraySlice.cs b/vsintegration/src/FSharp.PatternMatcher/ArraySlice.cs deleted file mode 100644 index 0e1edb2d26f..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/ArraySlice.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; - -namespace Roslyn.Utilities -{ - internal struct ArraySlice - { - private readonly T[] _array; - private int _start; - private int _length; - - public int Length => _length; - - public ArraySlice(T[] array) : this(array, 0, array.Length) - { - } - - public ArraySlice(T[] array, TextSpan span) : this(array, span.Start, span.Length) - { - } - - public ArraySlice(T[] array, int start, int length) : this() - { - _array = array; - SetStartAndLength(start, length); - } - - public T this[int i] - { - get - { - Debug.Assert(i < _length); - return _array[i + _start]; - } - } - - private void SetStartAndLength(int start, int length) - { - if (start < 0) - { - throw new ArgumentException(nameof(start), $"{start} < {0}"); - } - - if (start > _array.Length) - { - throw new ArgumentException(nameof(start), $"{start} > {_array.Length}"); - } - - CheckLength(start, length); - - _start = start; - _length = length; - } - - private void CheckLength(int start, int length) - { - if (length < 0) - { - throw new ArgumentException(nameof(length), $"{length} < {0}"); - } - - if (start + length > _array.Length) - { - throw new ArgumentException(nameof(start), $"{start} + {length} > {_array.Length}"); - } - } - - public void MoveStartForward(int amount) - { - SetStartAndLength(_start + amount, _length - amount); - } - - public void SetLength(int length) - { - CheckLength(_start, length); - _length = length; - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/BKTree.Builder.cs b/vsintegration/src/FSharp.PatternMatcher/BKTree.Builder.cs deleted file mode 100644 index b05c1bc3453..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/BKTree.Builder.cs +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Utilities; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; - -namespace Roslyn.Utilities -{ - internal partial class BKTree - { - private class Builder - { - // The number of edges we pre-allocate space for for each node in _compactEdges. - // - // To make the comments simpler below, i'll use '4' as a synonym for CompactEdgeAllocationSize. - // '4' simply reads better and makes it clearer what's going on. - private const int CompactEdgeAllocationSize = 4; - - // Instead of producing a char[] for each string we're building a node for, we instead - // have one long char[] with all the chracters of each string concatenated. i.e. - // "foo" "bar" and "baz" becomes { f, o, o, b, a, r, b, a, z }. Then in _wordSpans - // we have the text spans for each of those words in this array. This gives us only - // two allocations instead of as many allocations as the number of strings we have. - // - // Once we are done building, we pass this to the BKTree and its nodes also state the - // span of this array that corresponds to the word they were created for. This works - // well as other dependent facilities (like EditDistance) can work on sub-arrays without - // any problems. - private readonly char[] _concatenatedLowerCaseWords; - private readonly TextSpan[] _wordSpans; - - // Note: while building a BKTree we have to store children with parents, keyed by the - // edit distance between the two. Naive implementations might store a list or dictionary - // of children along with each node. However, this would be very inefficient and would - // put an enormous amount of memory pressure on the system. - // - // Emperical data for a nice large assembly like mscorlib gives us the following - // information: - // - // Unique-Words (ignoring case): 9662 - // - // For each unique word we need a node in the BKTree. If we stored a list or dictionary - // with each node, that would be 10s of thousands of objects created that would then - // just have to be GCed. That's a lot of garbage pressure we'd like to avoid. - // - // Now if we look at all those nodes, we can see the following information about how many - // children each has. - // - // Edge counts: - // 0 5560 - // 1 1884 - // 2 887 - // 3 527 - // 4 322 - // 5 200 - // 6 114 - // 7 69 - // 8 47 - // 9 20 - // 10 8 - // 11 10 - // 12 7 - // 13 4 - // 15 1 - // 16 1 - // 54 1 - // - // - // i.e. The number of nodes with edge-counts less than or equal to four is: 5560+1884+887+527+322=9180. - // This is 95% of the total number of edges we are adding. Looking at many other dlls - // we found that this ratio stays true across the board. i.e. with all dlls, 95% of nodes - // have 4 or less edges. - // - // So, to optimize things, we pre-alloc a single array with space for 4 edges for each - // node we're going to add. Each node then gets that much space to store edge information. - // If it needs more than that space, then we have a fall-over dictionary that it can store - // information in. - // - // Once building is complete, the GC only needs to deallocate this single array and the - // spillover dictionaries. - // - // This approach produces 1/20th the amount of garbage while building the tree. - // - // Each node at index i has its edges in this array in the range [4*i, 4*i + 4); - private readonly Edge[] _compactEdges; - private readonly BuilderNode[] _builderNodes; - - public Builder(IEnumerable values) - { - // TODO(cyrusn): Properly handle unicode normalization here. - var distinctValues = values.Where(v => v.Length > 0).Distinct(StringSliceComparer.OrdinalIgnoreCase).ToArray(); - var charCount = values.Sum(v => v.Length); - - _concatenatedLowerCaseWords = new char[charCount]; - _wordSpans = new TextSpan[distinctValues.Length]; - - var characterIndex = 0; - for (int i = 0; i < distinctValues.Length; i++) - { - var value = distinctValues[i]; - _wordSpans[i] = new TextSpan(characterIndex, value.Length); - - foreach (var ch in value) - { - _concatenatedLowerCaseWords[characterIndex] = CaseInsensitiveComparison.ToLower(ch); - characterIndex++; - } - } - - // We will have one node for each string value that we are adding. - _builderNodes = new BuilderNode[distinctValues.Length]; - _compactEdges = new Edge[distinctValues.Length * CompactEdgeAllocationSize]; - } - - internal BKTree Create() - { - for (var i = 0; i < _wordSpans.Length; i++) - { - Add(_wordSpans[i], insertionIndex: i); - } - - var nodes = ImmutableArray.CreateBuilder(_builderNodes.Length); - - // There will be one less edge in the graph than nodes. Each node (except for the - // root) will have a single edge pointing to it. - var edges = ImmutableArray.CreateBuilder(Math.Max(0, _builderNodes.Length - 1)); - - BuildArrays(nodes, edges); - - return new BKTree(_concatenatedLowerCaseWords, nodes.MoveToImmutable(), edges.MoveToImmutable()); - } - - private void BuildArrays(ImmutableArray.Builder nodes, ImmutableArray.Builder edges) - { - var currentEdgeIndex = 0; - for (var i = 0; i < _builderNodes.Length; i++) - { - var builderNode = _builderNodes[i]; - var edgeCount = builderNode.EdgeCount; - - nodes.Add(new Node(builderNode.CharacterSpan, edgeCount, currentEdgeIndex)); - - if (edgeCount > 0) - { - // First, copy any edges that are in the compact array. - var start = i * CompactEdgeAllocationSize; - var end = start + Math.Min(edgeCount, CompactEdgeAllocationSize); - for (var j = start; j < end; j++) - { - edges.Add(_compactEdges[j]); - } - - // Then, if we've spilled over any edges, copy them as well. - var spilledEdges = builderNode.SpilloverEdges; - if (spilledEdges != null) - { - Debug.Assert(spilledEdges.Count == (edgeCount - CompactEdgeAllocationSize)); - - foreach (var kvp in spilledEdges) - { - edges.Add(new Edge(kvp.Key, kvp.Value)); - } - } - } - - currentEdgeIndex += edgeCount; - } - - Debug.Assert(currentEdgeIndex == edges.Capacity); - Debug.Assert(currentEdgeIndex == edges.Count); - } - - private void Add(TextSpan characterSpan, int insertionIndex) - { - if (insertionIndex == 0) - { - _builderNodes[insertionIndex] = new BuilderNode(characterSpan); - return; - } - - var currentNodeIndex = 0; - while (true) - { - var currentNode = _builderNodes[currentNodeIndex]; - - // Determine the edit distance between these two words. Note: we do not use - // a threshold here as we need the actual edit distance so we can actually - // determine what edge to make or walk. - var editDistance = EditDistance.GetEditDistance( - new ArraySlice(_concatenatedLowerCaseWords, currentNode.CharacterSpan), - new ArraySlice(_concatenatedLowerCaseWords, characterSpan)); - - if (editDistance == 0) - { - // This should never happen. We dedupe all items before proceeding to the 'Add' step. - // So the edit distance should always be non-zero. - throw new InvalidOperationException(); - } - int childNodeIndex; - if (TryGetChildIndex(currentNode, currentNodeIndex, editDistance, out childNodeIndex)) - { - // Edit distances collide. Move to this child and add this word to it. - currentNodeIndex = childNodeIndex; - continue; - } - - // found the node we want to add the child node to. - AddChildNode(characterSpan, insertionIndex, currentNode.EdgeCount, currentNodeIndex, editDistance); - return; - } - } - - private void AddChildNode( - TextSpan characterSpan, int insertionIndex, int currentNodeEdgeCount, int currentNodeIndex, int editDistance) - { - // The node as 'currentNodeIndex' doesn't have an edge with this edit distance. - // Three cases to handle: - // 1) there are less than 4 edges. We simply place the edge into the correct - // location in compactEdges - // 2) there are 4 edges. We need to make the spillover dictionary and then add - // the new edge into that. - // 3) there are more than 4 edges. Just put the new edge in the spillover - // dictionary. - - if (currentNodeEdgeCount < CompactEdgeAllocationSize) - { - _compactEdges[currentNodeIndex * CompactEdgeAllocationSize + currentNodeEdgeCount] = - new Edge(editDistance, insertionIndex); - } - else - { - // When we hit 4 elements, we need to allocate the spillover dictionary to - // place the extra edges. - if (currentNodeEdgeCount == CompactEdgeAllocationSize) - { - Debug.Assert(_builderNodes[currentNodeIndex].SpilloverEdges == null); - var spilloverEdges = new Dictionary(); - _builderNodes[currentNodeIndex].SpilloverEdges = spilloverEdges; - } - - _builderNodes[currentNodeIndex].SpilloverEdges.Add(editDistance, insertionIndex); - } - - _builderNodes[currentNodeIndex].EdgeCount++; - _builderNodes[insertionIndex] = new BuilderNode(characterSpan); - return; - } - - private bool TryGetChildIndex(BuilderNode currentNode, int currentNodeIndex, int editDistance, out int childIndex) - { - // linearly scan the children we have to see if there is one with this edit distance. - var start = currentNodeIndex * CompactEdgeAllocationSize; - var end = start + Math.Min(currentNode.EdgeCount, CompactEdgeAllocationSize); - - for (var i = start; i < end; i++) - { - if (_compactEdges[i].EditDistance == editDistance) - { - childIndex = _compactEdges[i].ChildNodeIndex; - return true; - } - } - - // If we've spilled over any edges, check there as well - if (currentNode.SpilloverEdges != null) - { - // Can't use the compact array. Have to use the spillover dictionary instead. - Debug.Assert(currentNode.SpilloverEdges.Count == (currentNode.EdgeCount - CompactEdgeAllocationSize)); - return currentNode.SpilloverEdges.TryGetValue(editDistance, out childIndex); - } - - childIndex = -1; - return false; - } - - private struct BuilderNode - { - public readonly TextSpan CharacterSpan; - public int EdgeCount; - public Dictionary SpilloverEdges; - - public BuilderNode(TextSpan characterSpan) : this() - { - this.CharacterSpan = characterSpan; - } - } - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/BKTree.Edge.cs b/vsintegration/src/FSharp.PatternMatcher/BKTree.Edge.cs deleted file mode 100644 index c4dbf7daccc..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/BKTree.Edge.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -namespace Roslyn.Utilities -{ - internal partial class BKTree - { - private struct Edge - { - // The edit distance between the child and parent connected by this edge. - // The child can be found in _nodes at ChildNodeIndex. - public readonly int EditDistance; - - /// Where the child node can be found in . - public readonly int ChildNodeIndex; - - public Edge(int editDistance, int childNodeIndex) - { - EditDistance = editDistance; - ChildNodeIndex = childNodeIndex; - } - -#if false - internal void WriteTo(ObjectWriter writer) - { - writer.WriteInt32(EditDistance); - writer.WriteInt32(ChildNodeIndex); - } - - internal static Edge ReadFrom(ObjectReader reader) - { - return new Edge(editDistance: reader.ReadInt32(), childNodeIndex: reader.ReadInt32()); - } -#endif - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/BKTree.Node.cs b/vsintegration/src/FSharp.PatternMatcher/BKTree.Node.cs deleted file mode 100644 index 058be09a089..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/BKTree.Node.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -namespace Roslyn.Utilities -{ - internal partial class BKTree - { - private struct Node - { - /// - /// The string this node corresponds to. Specifically, this span is the range of - /// for that string. - /// - public readonly TextSpan WordSpan; - - ///How many child edges this node has. - public readonly int EdgeCount; - - ///Where the first edge can be found in . The edges - ///are in the range _edges[FirstEdgeIndex, FirstEdgeIndex + EdgeCount) - /// - public readonly int FirstEdgeIndex; - - public Node(TextSpan wordSpan, int edgeCount, int firstEdgeIndex) - { - WordSpan = wordSpan; - EdgeCount = edgeCount; - FirstEdgeIndex = firstEdgeIndex; - } - -#if false - internal void WriteTo(ObjectWriter writer) - { - writer.WriteInt32(WordSpan.Start); - writer.WriteInt32(WordSpan.Length); - writer.WriteInt32(EdgeCount); - writer.WriteInt32(FirstEdgeIndex); - } - - internal static Node ReadFrom(ObjectReader reader) - { - return new Node( - new TextSpan(start: reader.ReadInt32(), length: reader.ReadInt32()), - edgeCount: reader.ReadInt32(), firstEdgeIndex: reader.ReadInt32()); - } -#endif - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/BKTree.cs b/vsintegration/src/FSharp.PatternMatcher/BKTree.cs deleted file mode 100644 index 42cf609faf3..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/BKTree.cs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Utilities; -using System; - -namespace Roslyn.Utilities -{ - /// - /// NOTE: Only use if you truly need a BK-tree. If you just want to compare words, use - /// the type instead. - /// - /// An implementation of a Burkhard-Keller tree. Introduced in: - /// - /// 'Some approaches to best-match file searching.' - /// Communications of the ACM CACM - /// Volume 16 Issue 4, April 1973 - /// Pages 230-236 - /// http://dl.acm.org/citation.cfm?doid=362003.362025 - /// - internal partial class BKTree - { - private static char[] _emptyArray = new char[0]; - - public static readonly BKTree Empty = new BKTree( - _emptyArray, - ImmutableArray.Empty, - ImmutableArray.Empty); - - // We have three completely flat arrays of structs. These arrays fully represent the - // BK tree. The structure is as follows: - // - // The root node is in _nodes[0]. - // - // It lists the count of edges it has. These edges are in _edges in the range - // [0*, childCount). Each edge has the index of the child node it points to, and the - // edit distance between the parent and the child. - // - // * of course '0' is only for the root case. - // - // All nodes state where in _edges their child edges range starts, so the children - // for any node are in the range[node.FirstEdgeIndex, node.FirstEdgeIndex + node.EdgeCount). - // - // Each node also has an associated string. These strings are concatenated and stored - // in _concatenatedLowerCaseWords. Each node has a TextSpan that indicates which portion - // of the character array is their string. Note: i'd like to use an immutable array - // for the characters as well. However, we need to create slices, and they need to - // work on top of an ArraySlice (which needs a char[]). The edit distance code also - // wants to work on top of raw char[]s (both for speed, and so it can pool arrays - // to prevent lots of garbage). Because of that we just keep this as a char[]. - private readonly char[] _concatenatedLowerCaseWords; - private readonly ImmutableArray _nodes; - private readonly ImmutableArray _edges; - - private BKTree(char[] concatenatedLowerCaseWords, ImmutableArray nodes, ImmutableArray edges) - { - _concatenatedLowerCaseWords = concatenatedLowerCaseWords; - _nodes = nodes; - _edges = edges; - } - - public static BKTree Create(params string[] values) - { - return Create(values.Select(v => new StringSlice(v))); - } - - public static BKTree Create(IEnumerable values) - { - return new Builder(values).Create(); - } - - private static IList s_emptyList = new List().AsReadOnly(); - - public IList Find(string value, int? threshold = null) - { - if (_nodes.Length == 0) - { - return s_emptyList; - } - - var lowerCaseCharacters = ArrayPool.GetArray(value.Length); - try - { - for (var i = 0; i < value.Length; i++) - { - lowerCaseCharacters[i] = CaseInsensitiveComparison.ToLower(value[i]); - } - - threshold = threshold ?? WordSimilarityChecker.GetThreshold(value); - var result = new List(); - Lookup(_nodes[0], lowerCaseCharacters, value.Length, threshold.Value, result, recursionCount: 0); - return result; - } - finally - { - ArrayPool.ReleaseArray(lowerCaseCharacters); - } - } - - private void Lookup( - Node currentNode, - char[] queryCharacters, - int queryLength, - int threshold, - List result, - int recursionCount) - { - // Don't bother recursing too deeply in the case of pathological trees. - // This really only happens when the actual code is strange (like - // 10,000 symbols all a single letter long). In htat case, searching - // down this path will be fairly fruitless anyways. - // - // Note: this won't affect good searches against good data even if this - // pathological chain exists. That's because the good items will still - // cluster near the root node in the tree, and won't be off the end of - // this long chain. - if (recursionCount > 256) - { - return; - } - - // We always want to compute the real edit distance (ignoring any thresholds). This is - // because we need that edit distance to appropriately determine which edges to walk - // in the tree. - var characterSpan = currentNode.WordSpan; - var editDistance = EditDistance.GetEditDistance( - new ArraySlice(_concatenatedLowerCaseWords, characterSpan), - new ArraySlice(queryCharacters, 0, queryLength)); - - if (editDistance <= threshold) - { - // Found a match. - result.Add(new string(_concatenatedLowerCaseWords, characterSpan.Start, characterSpan.Length)); - } - - var min = editDistance - threshold; - var max = editDistance + threshold; - - var startInclusive = currentNode.FirstEdgeIndex; - var endExclusive = startInclusive + currentNode.EdgeCount; - for (var i = startInclusive; i < endExclusive; i++) - { - var childEditDistance = _edges[i].EditDistance; - if (min <= childEditDistance && childEditDistance <= max) - { - Lookup(_nodes[_edges[i].ChildNodeIndex], - queryCharacters, queryLength, threshold, result, - recursionCount + 1); - } - } - } - -#if false - // Used for diagnostic purposes. - internal void DumpStats() - { - var sb = new StringBuilder(); - sb.AppendLine("Nodes length: " + _nodes.Length); - var childCountHistogram = new Dictionary(); - - foreach (var node in _nodes) - { - var childCount = node.EdgeCount; - int existing; - childCountHistogram.TryGetValue(childCount, out existing); - - childCountHistogram[childCount] = existing + 1; - } - - sb.AppendLine(); - sb.AppendLine("Child counts:"); - foreach (var kvp in childCountHistogram.OrderBy(kvp => kvp.Key)) - { - sb.AppendLine(kvp.Key + "\t" + kvp.Value); - } - - // An item is dense if, starting from 1, at least 80% of it's array would be full. - var densities = new int[11]; - var empyCount = 0; - - foreach (var node in _nodes) - { - if (node.EdgeCount == 0) - { - empyCount++; - continue; - } - - var maxEditDistance = -1; - var startInclusive = node.FirstEdgeIndex; - var endExclusive = startInclusive + node.EdgeCount; - for (var i = startInclusive; i < endExclusive; i++) - { - maxEditDistance = Max(maxEditDistance, _edges[i].EditDistance); - } - - var editDistanceCount = node.EdgeCount; - - var bucket = 10 * editDistanceCount / maxEditDistance; - densities[bucket]++; - } - - var nonEmptyCount = _nodes.Length - empyCount; - sb.AppendLine(); - sb.AppendLine("NoChildren: " + empyCount); - sb.AppendLine("AnyChildren: " + nonEmptyCount); - sb.AppendLine("Densities:"); - for (var i = 0; i < densities.Length; i++) - { - sb.AppendLine("<=" + i + "0% = " + densities[i] + ", " + ((float)densities[i] / nonEmptyCount)); - } - - var result = sb.ToString(); - } -#endif - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/EditDistance.cs b/vsintegration/src/FSharp.PatternMatcher/EditDistance.cs deleted file mode 100644 index 7cfde4a8596..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/EditDistance.cs +++ /dev/null @@ -1,677 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Utilities; - -namespace Roslyn.Utilities -{ - /// - /// NOTE: Only use if you truly need an edit distance. If you just want to compare words, use - /// the type instead. - /// - /// Implementation of the Damerau-Levenshtein edit distance algorithm from: - /// An Extension of the String-to-String Correction Problem: - /// Published in Journal of the ACM (JACM) - /// Volume 22 Issue 2, April 1975. - /// - /// Important, unlike many edit distance algorithms out there, this one implements a true metric - /// that satisfies the triangle inequality. (Unlike the "Optimal String Alignment" or "Restricted - /// string edit distance" solutions which do not). This means this edit distance can be used in - /// other domains that require the triangle inequality (like BKTrees). - /// - /// Specifically, this implementation satisfies the following inequality: D(x, y) + D(y, z) >= D(x, z) - /// (where D is the edit distance). - /// - internal class EditDistance : IDisposable - { - // Our edit distance algorithm makes use of an 'infinite' value. A value so high that it - // could never participate in an edit distance (and effectively means the path through it - // is dead). - // - // We do *not* represent this with "int.MaxValue" due to the presence of certain addition - // operations in the edit distance algorithm. These additions could cause int.MaxValue - // to roll over to a very negative value (which would then look like the lowest cost - // path). - // - // So we pick a value that is both effectively larger than any possible edit distance, - // and also has no chance of overflowing. - private const int Infinity = int.MaxValue >> 1; - - public const int BeyondThreshold = int.MaxValue; - - private string _source; - private char[] _sourceLowerCaseCharacters; - - public EditDistance(string text) - { - if (text != null) - _source = text; - else - throw new ArgumentNullException("text"); - - _sourceLowerCaseCharacters = ConvertToLowercaseArray(text); - } - - private static char[] ConvertToLowercaseArray(string text) - { - var array = ArrayPool.GetArray(text.Length); - for (int i = 0; i < text.Length; i++) - { - array[i] = CaseInsensitiveComparison.ToLower(text[i]); - } - - return array; - } - - public void Dispose() - { - ArrayPool.ReleaseArray(_sourceLowerCaseCharacters); - _source = null; - _sourceLowerCaseCharacters = null; - } - - public static int GetEditDistance(string source, string target, int threshold = int.MaxValue) - { - using (var editDistance = new EditDistance(source)) - { - return editDistance.GetEditDistance(target, threshold); - } - } - - public static int GetEditDistance(char[] source, char[] target, int threshold = int.MaxValue) - { - return GetEditDistance(new ArraySlice(source), new ArraySlice(target), threshold); - } - - public int GetEditDistance(string target, int threshold = int.MaxValue) - { - if (_sourceLowerCaseCharacters == null) - { - throw new ObjectDisposedException(nameof(EditDistance)); - } - - var targetLowerCaseCharacters = ConvertToLowercaseArray(target); - try - { - return GetEditDistance( - new ArraySlice(_sourceLowerCaseCharacters, 0, _source.Length), - new ArraySlice(targetLowerCaseCharacters, 0, target.Length), - threshold); - } - finally - { - ArrayPool.ReleaseArray(targetLowerCaseCharacters); - } - } - - private const int MaxMatrixPoolDimension = 64; - private static readonly ThreadLocal t_matrixPool = - new ThreadLocal(() => InitializeMatrix(new int[MaxMatrixPoolDimension, MaxMatrixPoolDimension])); - - private static ThreadLocal> t_dictionaryPool = - new ThreadLocal>(() => new Dictionary()); - - private static int[,] GetMatrix(int width, int height) - { - if (width > MaxMatrixPoolDimension || height > MaxMatrixPoolDimension) - { - return InitializeMatrix(new int[width, height]); - } - - return t_matrixPool.Value; - } - - private static int[,] InitializeMatrix(int[,] matrix) - { - // All matrices share the following in common: - // - // ------------------ - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 7 - // |∞ 1 - // |∞ 2 - // |∞ 3 - // |∞ 4 - // |∞ 5 - // |∞ 6 - // |∞ 7 - // - // So we initialize this once when the matrix is created. For pooled arrays we only - // have to do this once, and it will retain this layout for all future computations. - - - var width = matrix.GetLength(0); - var height = matrix.GetLength(1); - - for (int i = 0; i < width; i++) - { - matrix[i, 0] = Infinity; - - if (i < width - 1) - { - matrix[i + 1, 1] = i; - } - } - - for (int j = 0; j < height; j++) - { - matrix[0, j] = Infinity; - - if (j < height - 1) - { - matrix[1, j + 1] = j; - } - } - - return matrix; - } - - public static int GetEditDistance(ArraySlice source, ArraySlice target, int threshold = int.MaxValue) - { - return source.Length <= target.Length - ? GetEditDistanceWorker(source, target, threshold) - : GetEditDistanceWorker(target, source, threshold); - } - - private static int GetEditDistanceWorker(ArraySlice source, ArraySlice target, int threshold) - { - // Note: sourceLength will always be smaller or equal to targetLength. - // - // Also Note: sourceLength and targetLength values will mutate and represent the lengths - // of the portions of the arrays we want to compare. However, even after mutation, hte - // invariant htat sourceLength is <= targetLength will remain. - Debug.Assert(source.Length <= target.Length); - - // First: - // Determine the common prefix/suffix portions of the strings. We don't even need to - // consider them as they won't add anything to the edit cost. - while (source.Length > 0 && source[source.Length - 1] == target[target.Length - 1]) - { - source.SetLength(source.Length - 1); - target.SetLength(target.Length - 1); - } - - while (source.Length > 0 && source[0] == target[0]) - { - source.MoveStartForward(amount: 1); - target.MoveStartForward(amount: 1); - } - - // 'sourceLength' and 'targetLength' are now the lengths of the substrings of our strings that we - // want to compare. 'startIndex' is the starting point of the substrings in both array. - // - // If we've matched all of the 'source' string in the prefix and suffix of 'target'. then the edit - // distance is just whatever operations we have to create the remaining target substring. - // - // Note: we don't have to check if targetLength is 0. That's because targetLength being zero would - // necessarily mean that sourceLength is 0. - var sourceLength = source.Length; - var targetLength = target.Length; - if (sourceLength == 0) - { - return targetLength <= threshold ? targetLength : BeyondThreshold; - } - - // The is the minimum number of edits we'd have to make. i.e. if 'source' and - // 'target' are the same length, then we might not need to make any edits. However, - // if target has length 10 and source has length 7, then we're going to have to - // make at least 3 edits no matter what. - var minimumEditCount = targetLength - sourceLength; - Debug.Assert(minimumEditCount >= 0); - - // If the number of edits we'd have to perform is greater than our threshold, then - // there's no point in even continuing. - if (minimumEditCount > threshold) - { - return BeyondThreshold; - } - - // Say we want to find the edit distance between "sunday" and "saturday". Our initial - // matrix will be: - // - // (Note: for purposes of this explanation we will not be trimming off the common - // prefix/suffix of the strings. That optimization does not affect any of the - // remainder of the explanation). - // - // s u n d a y - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 - // a |∞ 2 - // t |∞ 3 - // u |∞ 4 - // r |∞ 5 - // d |∞ 6 - // a |∞ 7 - // y |∞ 8 - // - // Note that the matrix will always be square, or a rectangle that is taller htan it is - // longer. Our 'source' is at the top, and our 'target' is on the left. The edit distance - // between any prefix of 'source' and any prefix of 'target' can then be found in - // the unfilled area of the matrix. Specifically, if we have source.substring(0, m) and - // target.substring(0, n), then the edit distance for them can be found at matrix position - // (m+1, n+1). This is why the 1'th row and 1'th column can be prefilled. They represent - // the cost to go from the empty target to the full source or the empty source to the full - // target (respectively). So, if we wanted to know the edit distance between "sun" and - // "sat", we'd look at (3+1, 3+1). It then follows that our final edit distance between - // the full source and target is in the lower right corner of this matrix. - // - // If we fill out the matrix fully we'll get: - // - // s u n d a y <-- source - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 0 1 2 3 4 5 - // a |∞ 2 1 1 2 3 3 4 - // t |∞ 3 2 2 2 3 4 4 - // u |∞ 4 3 2 3 3 4 5 - // r |∞ 5 4 3 3 4 4 5 - // d |∞ 6 5 4 4 3 4 5 - // a |∞ 7 6 5 5 4 3 4 - // y |∞ 8 7 6 6 5 4 3 <-- - // ^ - // | - // - // So in this case, the edit distance is 3. Or, specifically, the edits: - // - // Sunday -> Replace("n", "r") -> - // Surday -> Insert("a") -> - // Saurday -> Insert("t") -> - // Saturday - // - // - // Now: in the case where we want to know what the edit distance actually is (for example - // when making a BKTree), we must fill out this entire array to get the true edit distance. - // - // However, in some cases we can do a bit better. For example, if a client only wants to - // the edit distance *when the edit distance will be less than some threshold* then we do - // not need to examine the entire matrix. We only want to examine until the point where - // we realize that, no matter what, our final edit distance will be more than that threshold - // (at which point we can return early). - // - // Some things are trivially easy to check. First, the edit distance between two strings is at - // *best* the difference of their lengths. i.e. if i have "aa" and "aaaaa" then the edit - // distance is 3 (the difference of 5 and 2). If our threshold is less then 3 then there - // is no way these two strings could match. So we can leave early if we can tell it would - // simply be impossible to get an edit distance within the specified threshold. - // - // Second, let's look at our matrix again: - // - // s u n d a y - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 - // a |∞ 2 - // t |∞ 3 - // u |∞ 4 - // r |∞ 5 - // d |∞ 6 - // a |∞ 7 - // y |∞ 8 * - // - // We want to know what the value is at *, and we want to stop as early as possible if it - // is greater than our threshold. - // - // Given the edit distance rules we observe edit distance at any point (i,j) in the matrix will - // always be greater than or equal to the value in (i-1, j-1). i.e. the edit distance of - // any two strings is going to be *at best* equal to the edit distance of those two strings - // without their final characters. If their final characters are the same, they'll have the - // same edit distance. If they are different, the edit distance will be greater. Given - // that we know the final edit distance is in the lower right, we can discover something - // useful in the matrix. - // - // s u n d a y - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 - // a |∞ 2 - // t |∞ 3 ` - // u |∞ 4 ` - // r |∞ 5 ` - // d |∞ 6 ` - // a |∞ 7 ` - // y |∞ 8 * - // - // The slashes are the "bottom" diagonal leading to the lower right. The value in the - // lower right will be strictly equal to or greater than any value on this diagonal. - // Thus, if that value exceeds the threshold, we know we can stop immediately as the - // total edit distance must be greater than the threshold. - // - // We can use similar logic to avoid even having to examine more of the matrix when we - // have a threshold. First, consider the same diagonal. - // - // s u n d a y - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 - // a |∞ 2 - // t |∞ 3 ` - // u |∞ 4 ` x - // r |∞ 5 ` | - // d |∞ 6 ` | - // a |∞ 7 ` | - // y |∞ 8 * - // - // And then consider a point above that diagonal (indicated by x). In the example - // above, the edit distance to * from 'x' will be (x+4). If, for example, threshold - // was '2', then it would be impossible for the path from 'x' to provide a good - // enough edit distance *ever*. Similarly: - // - // s u n d a y - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 - // a |∞ 2 - // t |∞ 3 ` - // u |∞ 4 ` - // r |∞ 5 ` - // d |∞ 6 ` - // a |∞ 7 ` - // y |∞ 8 y - - * - // - // Here we see that the final edit distance will be "y+3". Again, if the edit - // distance threshold is less than 3, then no path from y will provide a good - // enough edit distance. - // - // So, if we had an edit distance threshold of 3, then the range around that - // bottom diagonal that we should consider checking is: - // - // s u n d a y - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 | | - // a |∞ 2 | | | - // t |∞ 3 ` | | | - // u |∞ 4 - ` | | | - // r |∞ 5 - - ` | | | - // d |∞ 6 - - - ` | | - // a |∞ 7 - - - ` | - // y |∞ 8 - - - * - // - // Now, also consider that it will take a minimum of targetLength-sourceLength edits - // just to move to the lower diagonal from the upper diagonal. That leaves - // 'threshold - (targetLength - sourceLength)' edits remaining. In this example, that - // means '3 - (8 - 6)' = 1. Because of this our lower diagonal offset is capped at: - // - // s u n d a y - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 | | - // a |∞ 2 | | | - // t |∞ 3 ` | | | - // u |∞ 4 - ` | | | - // r |∞ 5 - ` | | | - // d |∞ 6 - ` | | - // a |∞ 7 - ` | - // y |∞ 8 - * - // - // If we mark the upper diagonal appropriately we see the matrix as: - // - // s u n d a y - // ---------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 - // s |∞ 1 ` | - // a |∞ 2 ` | - // t |∞ 3 ` ` | - // u |∞ 4 - ` ` | - // r |∞ 5 - ` ` | - // d |∞ 6 - ` ` - // a |∞ 7 - ` - // y |∞ 8 - * - // - // Or, effectively, we only need to examine 'threshold - (targetLength - sourceLength)' - // above and below the diagonals. - // - // In practice, when a threshold is provided it is normally capped at '2'. Given that, - // the most around the diagonal we'll ever have to check is +/- 2 elements. i.e. with - // strings of length 10 we'd only check: - // - // a b c d e f g h i j - // ------------------------ - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 7 8 9 10 - // m |∞ 1 * * * - // n |∞ 2 * * * * - // o |∞ 3 * * * * * - // p |∞ 4 * * * * * - // q |∞ 5 * * * * * - // r |∞ 6 * * * * * - // s |∞ 7 * * * * * - // t |∞ 8 * * * * * - // u |∞ 9 * * * * - // v |∞10 * * * - // - // or 10+18+16=44. Or only 44%. if our threshold is two and our strings differ by length - // 2 then we have: - // - // a b c d e f g h - // -------------------- - // |∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ - // |∞ 0 1 2 3 4 5 6 7 8 - // m |∞ 1 * - // n |∞ 2 * * - // o |∞ 3 * * * - // p |∞ 4 * * * - // q |∞ 5 * * * - // r |∞ 6 * * * - // s |∞ 7 * * * - // t |∞ 8 * * * - // u |∞ 9 * * - // v |∞10 * - // - // Then we examine 8+8+8=24 out of 80, or only 30% of the matrix. As the strings - // get larger, the savings increase as well. - - // -------------------------------------------------------------------------------- - - // The highest cost it can be to convert a source to target is targetLength. i.e. - // changing all the characters in source to target (which would be be 'sourceLength' - // changes), and then adding all the missing characters in 'target' (which is - // 'targetLength' - 'sourceLength' changes). Combined that's 'targetLength'. - // - // So we can just cap our threshold here. This makes some of the walking code - // below simpler. - threshold = Math.Min(threshold, targetLength); - - var offset = threshold - minimumEditCount; - Debug.Assert(offset >= 0); - - var matrix = GetMatrix(sourceLength + 2, targetLength + 2); - - var characterToLastSeenIndex_inSource = t_dictionaryPool.Value; - characterToLastSeenIndex_inSource.Clear(); - - for (int i = 1; i <= sourceLength; i++) - { - var lastMatchIndex_inTarget = 0; - var sourceChar = source[i - 1]; - - // Determinethe portion of the column we actually want to examine. - var jStart = Math.Max(1, i - offset); - var jEnd = Math.Min(targetLength, i + minimumEditCount + offset); - - // If we're examining only a subportion of the column, then we need to make sure - // that the values outside that range are set to Infinity. That way we don't - // consider them when we look through edit paths from above (for this column) or - // from the left (for the next column). - if (jStart > 1) - { - matrix[i + 1, jStart] = Infinity; - } - - if (jEnd < targetLength) - { - matrix[i + 1, jEnd + 2] = Infinity; - } - - for (int j = jStart; j <= jEnd; j++) - { - var targetChar = target[j - 1]; - - var i1 = GetValue(characterToLastSeenIndex_inSource, targetChar); - var j1 = lastMatchIndex_inTarget; - - var matched = sourceChar == targetChar; - if (matched) - { - lastMatchIndex_inTarget = j; - } - - matrix[i + 1, j + 1] = Min( - matrix[i, j] + (matched ? 0 : 1), - matrix[i + 1, j] + 1, - matrix[i, j + 1] + 1, - matrix[i1, j1] + (i - i1 - 1) + 1 + (j - j1 - 1)); - } - - characterToLastSeenIndex_inSource[sourceChar] = i; - - // Recall that minimumEditCount is simply the difference in length of our two - // strings. So matrix[i+1,i+1] is the cost for the upper-left diagonal of the - // matrix. matrix[i+1,i+1+minimumEditCount] is the cost for the lower right diagonal. - // Here we are simply getting the lowest cost edit of hese two substrings so far. - // If this lowest cost edit is greater than our threshold, then there is no need - // to proceed. - if (matrix[i + 1, i + minimumEditCount + 1] > threshold) - { - return BeyondThreshold; - } - } - - return matrix[sourceLength + 1, targetLength + 1]; - } - - private static string ToString(int[,] matrix, int width, int height) - { - var sb = new StringBuilder(); - for (var j = 0; j < height; j++) - { - for (var i = 0; i < width; i++) - { - var v = matrix[i + 2, j + 2]; - sb.Append((v == Infinity ? "∞" : v.ToString()) + " "); - } - sb.AppendLine(); - } - - return sb.ToString().Trim(); - } - - private static int GetValue(Dictionary da, char c) - { - int value; - return da.TryGetValue(c, out value) ? value : 0; - } - - private static int Min(int v1, int v2, int v3, int v4) - { - Debug.Assert(v1 >= 0); - Debug.Assert(v2 >= 0); - Debug.Assert(v3 >= 0); - Debug.Assert(v4 >= 0); - - var min = v1; - if (v2 < min) - { - min = v2; - } - - if (v3 < min) - { - min = v3; - } - - if (v4 < min) - { - min = v4; - } - - Debug.Assert(min >= 0); - return min; - } - - private static void SetValue(int[,] matrix, int i, int j, int val) - { - // Matrix is -1 based, so we add 1 to both i and j to make it - // possible to index into the actual storage. - matrix[i + 1, j + 1] = val; - } - } - - internal class SimplePool where T : class - { - private readonly object _gate = new object(); - private readonly Stack _values = new Stack(); - private readonly Func _allocate; - - public SimplePool(Func allocate) - { - _allocate = allocate; - } - - public T Allocate() - { - lock (_gate) - { - if (_values.Count > 0) - { - return _values.Pop(); - } - - return _allocate(); - } - } - - public void Free(T value) - { - lock (_gate) - { - _values.Push(value); - } - } - } - - internal static class ArrayPool - { - private const int MaxPooledArraySize = 256; - - // Keep around a few arrays of size 256 that we can use for operations without - // causing lots of garbage to be created. If we do compare items larger than - // that, then we will just allocate and release those arrays on demand. - private static SimplePool s_pool = new SimplePool(() => new T[MaxPooledArraySize]); - - public static T[] GetArray(int size) - { - if (size <= MaxPooledArraySize) - { - var array = s_pool.Allocate(); - Array.Clear(array, 0, array.Length); - return array; - } - - return new T[size]; - } - - public static void ReleaseArray(T[] array) - { - if (array.Length <= MaxPooledArraySize) - { - s_pool.Free(array); - } - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/FSharp.PatternMatcher.csproj b/vsintegration/src/FSharp.PatternMatcher/FSharp.PatternMatcher.csproj deleted file mode 100644 index 20617fd7527..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/FSharp.PatternMatcher.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - Library - true - false - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/Hash.cs b/vsintegration/src/FSharp.PatternMatcher/Hash.cs deleted file mode 100644 index ee26381440b..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/Hash.cs +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Utilities; - -namespace Roslyn.Utilities -{ - internal static class Hash - { - /// - /// This is how VB Anonymous Types combine hash values for fields. - /// - internal static int Combine(int newKey, int currentKey) - { - return unchecked((currentKey * (int)0xA5555529) + newKey); - } - - internal static int Combine(bool newKeyPart, int currentKey) - { - return Combine(currentKey, newKeyPart ? 1 : 0); - } - - /// - /// This is how VB Anonymous Types combine hash values for fields. - /// PERF: Do not use with enum types because that involves multiple - /// unnecessary boxing operations. Unfortunately, we can't constrain - /// T to "non-enum", so we'll use a more restrictive constraint. - /// - internal static int Combine(T newKeyPart, int currentKey) where T : class - { - int hash = unchecked(currentKey * (int)0xA5555529); - - if (newKeyPart != null) - { - return unchecked(hash + newKeyPart.GetHashCode()); - } - - return hash; - } - - internal static int CombineValues(IEnumerable values, int maxItemsToHash = int.MaxValue) - { - if (values == null) - { - return 0; - } - - var hashCode = 0; - var count = 0; - foreach (var value in values) - { - if (count++ >= maxItemsToHash) - { - break; - } - - // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). - if (value != null) - { - hashCode = Hash.Combine(value.GetHashCode(), hashCode); - } - } - - return hashCode; - } - - internal static int CombineValues(T[] values, int maxItemsToHash = int.MaxValue) - { - if (values == null) - { - return 0; - } - - var maxSize = Math.Min(maxItemsToHash, values.Length); - var hashCode = 0; - - for (int i = 0; i < maxSize; i++) - { - T value = values[i]; - - // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). - if (value != null) - { - hashCode = Hash.Combine(value.GetHashCode(), hashCode); - } - } - - return hashCode; - } - - internal static int CombineValues(ImmutableArray values, int maxItemsToHash = int.MaxValue) - { - if (values.IsDefaultOrEmpty) - { - return 0; - } - - var hashCode = 0; - var count = 0; - foreach (var value in values) - { - if (count++ >= maxItemsToHash) - { - break; - } - - // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). - if (value != null) - { - hashCode = Hash.Combine(value.GetHashCode(), hashCode); - } - } - - return hashCode; - } - - internal static int CombineValues(IEnumerable values, StringComparer stringComparer, int maxItemsToHash = int.MaxValue) - { - if (values == null) - { - return 0; - } - - var hashCode = 0; - var count = 0; - foreach (var value in values) - { - if (count++ >= maxItemsToHash) - { - break; - } - - if (value != null) - { - hashCode = Hash.Combine(stringComparer.GetHashCode(value), hashCode); - } - } - - return hashCode; - } - - /// - /// The offset bias value used in the FNV-1a algorithm - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - internal const int FnvOffsetBias = unchecked((int)2166136261); - - /// - /// The generative factor used in the FNV-1a algorithm - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - internal const int FnvPrime = 16777619; - - /// - /// Compute the FNV-1a hash of a sequence of bytes - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The sequence of bytes - /// The FNV-1a hash of - internal static int GetFNVHashCode(byte[] data) - { - int hashCode = Hash.FnvOffsetBias; - - for (int i = 0; i < data.Length; i++) - { - hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); - } - - return hashCode; - } - -#if false - /// - /// Compute the FNV-1a hash of a sequence of bytes and determines if the byte - /// sequence is valid ASCII and hence the hash code matches a char sequence - /// encoding the same text. - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The sequence of bytes that are likely to be ASCII text. - /// The length of the sequence. - /// True if the sequence contains only characters in the ASCII range. - /// The FNV-1a hash of - internal static unsafe int GetFNVHashCode(byte* data, int length, out bool isAscii) - { - int hashCode = Hash.FnvOffsetBias; - - byte asciiMask = 0; - - for (int i = 0; i < length; i++) - { - byte b = data[i]; - asciiMask |= b; - hashCode = unchecked((hashCode ^ b) * Hash.FnvPrime); - } - - isAscii = (asciiMask & 0x80) == 0; - return hashCode; - } -#endif - - /// - /// Compute the FNV-1a hash of a sequence of bytes - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The sequence of bytes - /// The FNV-1a hash of - internal static int GetFNVHashCode(ImmutableArray data) - { - int hashCode = Hash.FnvOffsetBias; - - for (int i = 0; i < data.Length; i++) - { - hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a sub-string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// Note: FNV-1a was developed and tuned for 8-bit sequences. We're using it here - /// for 16-bit Unicode chars on the understanding that the majority of chars will - /// fit into 8-bits and, therefore, the algorithm will retain its desirable traits - /// for generating hash codes. - /// - /// The input string - /// The start index of the first character to hash - /// The number of characters, beginning with to hash - /// The FNV-1a hash code of the substring beginning at and ending after characters. - internal static int GetFNVHashCode(string text, int start, int length) - { - int hashCode = Hash.FnvOffsetBias; - int end = start + length; - - for (int i = start; i < end; i++) - { - hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - internal static int GetCaseInsensitiveFNVHashCode(string text, int start, int length) - { - int hashCode = Hash.FnvOffsetBias; - int end = start + length; - - for (int i = start; i < end; i++) - { - hashCode = unchecked((hashCode ^ CaseInsensitiveComparison.ToLower(text[i])) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a sub-string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The input string - /// The start index of the first character to hash - /// The FNV-1a hash code of the substring beginning at and ending at the end of the string. - internal static int GetFNVHashCode(string text, int start) - { - return GetFNVHashCode(text, start, length: text.Length - start); - } - - /// - /// Compute the hashcode of a string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The input string - /// The FNV-1a hash code of - internal static int GetFNVHashCode(string text) - { - return CombineFNVHash(Hash.FnvOffsetBias, text); - } - - /// - /// Compute the hashcode of a string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The input string - /// The FNV-1a hash code of - internal static int GetFNVHashCode(System.Text.StringBuilder text) - { - int hashCode = Hash.FnvOffsetBias; - int end = text.Length; - - for (int i = 0; i < end; i++) - { - hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a sub string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The input string as a char array - /// The start index of the first character to hash - /// The number of characters, beginning with to hash - /// The FNV-1a hash code of the substring beginning at and ending after characters. - internal static int GetFNVHashCode(char[] text, int start, int length) - { - int hashCode = Hash.FnvOffsetBias; - int end = start + length; - - for (int i = start; i < end; i++) - { - hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a single character using the FNV-1a algorithm - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// Note: In general, this isn't any more useful than "char.GetHashCode". However, - /// it may be needed if you need to generate the same hash code as a string or - /// substring with just a single character. - /// - /// The character to hash - /// The FNV-1a hash code of the character. - internal static int GetFNVHashCode(char ch) - { - return Hash.CombineFNVHash(Hash.FnvOffsetBias, ch); - } - - /// - /// Combine a string with an existing FNV-1a hash code - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The accumulated hash code - /// The string to combine - /// The result of combining with using the FNV-1a algorithm - internal static int CombineFNVHash(int hashCode, string text) - { - foreach (char ch in text) - { - hashCode = unchecked((hashCode ^ ch) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Combine a char with an existing FNV-1a hash code - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The accumulated hash code - /// The new character to combine - /// The result of combining with using the FNV-1a algorithm - internal static int CombineFNVHash(int hashCode, char ch) - { - return unchecked((hashCode ^ ch) * Hash.FnvPrime); - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/IDictionaryExtensions.cs b/vsintegration/src/FSharp.PatternMatcher/IDictionaryExtensions.cs deleted file mode 100644 index 899d7388e3c..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/IDictionaryExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -namespace Roslyn.Utilities -{ - internal static class IDictionaryExtensions - { - // Copied from ConcurrentDictionary since IDictionary doesn't have this useful method - public static V GetOrAdd(this IDictionary dictionary, K key, Func function) - { - V value; - if (!dictionary.TryGetValue(key, out value)) - { - value = function(key); - dictionary.Add(key, value); - } - - return value; - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/IObjectWritable.cs b/vsintegration/src/FSharp.PatternMatcher/IObjectWritable.cs deleted file mode 100644 index 15f286884f2..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/IObjectWritable.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.CodeAnalysis -{ - /// - /// Objects that implement this interface know how to write their contents to an - /// - internal interface IObjectWritable - { - void WriteTo(ObjectWriter writer); - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/ImmutableArrayExtensions.cs b/vsintegration/src/FSharp.PatternMatcher/ImmutableArrayExtensions.cs deleted file mode 100644 index 588fa762417..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/ImmutableArrayExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System.Collections.Immutable; - -namespace Roslyn.Utilities -{ - internal static class ImmutableArrayExtensions - { - public static ImmutableArray NullToEmpty(this ImmutableArray array) - { - return array.IsDefault ? ImmutableArray.Empty : array; - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/NormalizedTextSpanCollection.cs b/vsintegration/src/FSharp.PatternMatcher/NormalizedTextSpanCollection.cs deleted file mode 100644 index 82e439c58b5..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/NormalizedTextSpanCollection.cs +++ /dev/null @@ -1,630 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Shared -{ - internal class NormalizedTextSpanCollection : ReadOnlyCollection - { - /// - /// Initializes a new instance of that is - /// empty. - /// - public NormalizedTextSpanCollection() - : base(new List(0)) - { - } - - /// - /// Initializes a new instance of that contains the specified span. - /// - /// TextSpan contained by the span set. - public NormalizedTextSpanCollection(TextSpan span) - : base(ListFromSpan(span)) - { - } - - /// - /// Initializes a new instance of that contains the specified list of spans. - /// - /// The spans to be added. - /// - /// The list of spans will be sorted and normalized (overlapping and adjoining spans will be combined). - /// This constructor runs in O(N log N) time, where N = spans.Count. - /// is null. - public NormalizedTextSpanCollection(IEnumerable spans) - : base(NormalizedTextSpanCollection.NormalizeSpans(spans)) - { - // NormalizeSpans will throw if spans == null. - } - - /// - /// Finds the union of two span sets. - /// - /// - /// The first span set. - /// - /// - /// The second span set. - /// - /// - /// The new span set that corresponds to the union of and . - /// - /// This operator runs in O(N+M) time where N = left.Count, M = right.Count. - /// Either or is null. - public static NormalizedTextSpanCollection Union(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right) - { - if (left == null) - { - throw new ArgumentNullException(nameof(left)); - } - - if (right == null) - { - throw new ArgumentNullException(nameof(right)); - } - - if (left.Count == 0) - { - return right; - } - - if (right.Count == 0) - { - return left; - } - - OrderedSpanList spans = new OrderedSpanList(); - - int index1 = 0; - int index2 = 0; - - int start = -1; - int end = int.MaxValue; - while ((index1 < left.Count) && (index2 < right.Count)) - { - TextSpan span1 = left[index1]; - TextSpan span2 = right[index2]; - - if (span1.Start < span2.Start) - { - NormalizedTextSpanCollection.UpdateSpanUnion(span1, spans, ref start, ref end); - ++index1; - } - else - { - NormalizedTextSpanCollection.UpdateSpanUnion(span2, spans, ref start, ref end); - ++index2; - } - } - - while (index1 < left.Count) - { - NormalizedTextSpanCollection.UpdateSpanUnion(left[index1], spans, ref start, ref end); - ++index1; - } - - while (index2 < right.Count) - { - NormalizedTextSpanCollection.UpdateSpanUnion(right[index2], spans, ref start, ref end); - ++index2; - } - - if (end != int.MaxValue) - { - spans.Add(TextSpan.FromBounds(start, end)); - } - - return new NormalizedTextSpanCollection(spans); - } - - /// - /// Finds the overlap of two span sets. - /// - /// The first span set. - /// The second span set. - /// The new span set that corresponds to the overlap of and . - /// This operator runs in O(N+M) time where N = left.Count, M = right.Count. - /// or is null. - public static NormalizedTextSpanCollection Overlap(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right) - { - if (left == null) - { - throw new ArgumentNullException(nameof(left)); - } - - if (right == null) - { - throw new ArgumentNullException(nameof(right)); - } - - if (left.Count == 0) - { - return left; - } - - if (right.Count == 0) - { - return right; - } - - OrderedSpanList spans = new OrderedSpanList(); - for (int index1 = 0, index2 = 0; (index1 < left.Count) && (index2 < right.Count);) - { - TextSpan span1 = left[index1]; - TextSpan span2 = right[index2]; - - if (span1.OverlapsWith(span2)) - { - spans.Add(span1.Overlap(span2).Value); - } - - if (span1.End < span2.End) - { - ++index1; - } - else if (span1.End == span2.End) - { - ++index1; - ++index2; - } - else - { - ++index2; - } - } - - return new NormalizedTextSpanCollection(spans); - } - - /// - /// Finds the intersection of two span sets. - /// - /// The first span set. - /// The second span set. - /// The new span set that corresponds to the intersection of and . - /// This operator runs in O(N+M) time where N = left.Count, M = right.Count. - /// is null. - /// is null. - public static NormalizedTextSpanCollection Intersection(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right) - { - if (left == null) - { - throw new ArgumentNullException(nameof(left)); - } - - if (right == null) - { - throw new ArgumentNullException(nameof(right)); - } - - if (left.Count == 0) - { - return left; - } - - if (right.Count == 0) - { - return right; - } - - OrderedSpanList spans = new OrderedSpanList(); - for (int index1 = 0, index2 = 0; (index1 < left.Count) && (index2 < right.Count);) - { - TextSpan span1 = left[index1]; - TextSpan span2 = right[index2]; - - if (span1.IntersectsWith(span2)) - { - spans.Add(span1.Intersection(span2).Value); - } - - if (span1.End < span2.End) - { - ++index1; - } - else - { - ++index2; - } - } - - return new NormalizedTextSpanCollection(spans); - } - - /// - /// Finds the difference between two sets. The difference is defined as everything in the first span set that is not in the second span set. - /// - /// The first span set. - /// The second span set. - /// The new span set that corresponds to the difference between and . - /// - /// Empty spans in the second set do not affect the first set at all. This method returns empty spans in the first set that are not contained by any set in - /// the second set. - /// - /// is null. - /// is null. - public static NormalizedTextSpanCollection Difference(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right) - { - if (left == null) - { - throw new ArgumentNullException(nameof(left)); - } - - if (right == null) - { - throw new ArgumentNullException(nameof(right)); - } - - if (left.Count == 0) - { - return left; - } - - if (right.Count == 0) - { - return left; - } - - OrderedSpanList spans = new OrderedSpanList(); - - int index1 = 0; - int index2 = 0; - int lastEnd = -1; - do - { - TextSpan span1 = left[index1]; - TextSpan span2 = right[index2]; - - if ((span2.Length == 0) || (span1.Start >= span2.End)) - { - ++index2; - } - else if (span1.End <= span2.Start) - { - // lastEnd is set to the end of the previously encountered intersecting span - // from right when it ended before the end of span1 (so it must still be less - // than the end of span1). - Debug.Assert(lastEnd < span1.End); - spans.Add(TextSpan.FromBounds(Math.Max(lastEnd, span1.Start), span1.End)); - ++index1; - } - else - { - // The spans intersect, so add anything from span1 that extends to the left of span2. - if (span1.Start < span2.Start) - { - // lastEnd is set to the end of the previously encountered intersecting span - // on span2, so it must be less than the start of the current span on span2. - Debug.Assert(lastEnd < span2.Start); - spans.Add(TextSpan.FromBounds(Math.Max(lastEnd, span1.Start), span2.Start)); - } - - if (span1.End < span2.End) - { - ++index1; - } - else if (span1.End == span2.End) - { - // Both spans ended at the same place so we're done with both. - ++index1; - ++index2; - } - else - { - // span2 ends before span1, so keep track of where it ended so that we don't - // try to add the excluded portion the next time we add a span. - lastEnd = span2.End; - ++index2; - } - } - } - while ((index1 < left.Count) && (index2 < right.Count)); - - while (index1 < left.Count) - { - TextSpan span1 = left[index1++]; - spans.Add(TextSpan.FromBounds(Math.Max(lastEnd, span1.Start), span1.End)); - } - - return new NormalizedTextSpanCollection(spans); - } - - /// - /// Determines whether two span sets are the same. - /// - /// The first set. - /// The second set. - /// true if the two sets are equivalent, otherwise false. - public static bool operator ==(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right) - { - if (object.ReferenceEquals(left, right)) - { - return true; - } - - if (object.ReferenceEquals(left, null) || object.ReferenceEquals(right, null)) - { - return false; - } - - if (left.Count != right.Count) - { - return false; - } - - for (int i = 0; i < left.Count; ++i) - { - if (left[i] != right[i]) - { - return false; - } - } - - return true; - } - - /// - /// Determines whether two span sets are not the same. - /// - /// The first set. - /// The second set. - /// true if the two sets are not equivalent, otherwise false. - public static bool operator !=(NormalizedTextSpanCollection left, NormalizedTextSpanCollection right) - { - return !(left == right); - } - - /// - /// Determines whether this span set overlaps with another span set. - /// - /// The span set to test. - /// true if the span sets overlap, otherwise false. - /// is null. - public bool OverlapsWith(NormalizedTextSpanCollection set) - { - if (set == null) - { - throw new ArgumentNullException(nameof(set)); - } - - for (int index1 = 0, index2 = 0; (index1 < this.Count) && (index2 < set.Count);) - { - TextSpan span1 = this[index1]; - TextSpan span2 = set[index2]; - - if (span1.OverlapsWith(span2)) - { - return true; - } - - if (span1.End < span2.End) - { - ++index1; - } - else if (span1.End == span2.End) - { - ++index1; - ++index2; - } - else - { - ++index2; - } - } - - return false; - } - - /// - /// Determines whether this span set overlaps with another span. - /// - /// The span to test. - /// true if this span set overlaps with the given span, otherwise false. - public bool OverlapsWith(TextSpan span) - { - // TODO: binary search - for (int index = 0; index < this.Count; ++index) - { - if (this[index].OverlapsWith(span)) - { - return true; - } - } - - return false; - } - - /// - /// Determines whether this span set intersects with another span set. - /// - /// Set to test. - /// true if the span sets intersect, otherwise false. - /// is null. - public bool IntersectsWith(NormalizedTextSpanCollection set) - { - if (set == null) - { - throw new ArgumentNullException(nameof(set)); - } - - for (int index1 = 0, index2 = 0; (index1 < this.Count) && (index2 < set.Count);) - { - TextSpan span1 = this[index1]; - TextSpan span2 = set[index2]; - - if (span1.IntersectsWith(span2)) - { - return true; - } - - if (span1.End < span2.End) - { - ++index1; - } - else - { - ++index2; - } - } - - return false; - } - - /// - /// Determines whether this span set intersects with another span. - /// - /// true if this span set intersects with the given span, otherwise false. - public bool IntersectsWith(TextSpan span) - { - // TODO: binary search - for (int index = 0; index < this.Count; ++index) - { - if (this[index].IntersectsWith(span)) - { - return true; - } - } - - return false; - } - - #region Overridden methods and operators - - /// - /// Gets a unique hash code for the span set. - /// - /// A 32-bit hash code associated with the set. - public override int GetHashCode() - { - int hc = 0; - foreach (TextSpan s in this) - { - hc ^= s.GetHashCode(); - } - - return hc; - } - - /// - /// Determines whether this span set is the same as another object. - /// - /// The object to test. - /// true if the two objects are equal, otherwise false. - public override bool Equals(object obj) - { - NormalizedTextSpanCollection set = obj as NormalizedTextSpanCollection; - - return this == set; - } - - /// - /// Provides a string representation of the set. - /// - /// The string representation of the set. - public override string ToString() - { - StringBuilder value = new StringBuilder("{"); - foreach (TextSpan s in this) - { - value.Append(s.ToString()); - } - - value.Append("}"); - - return value.ToString(); - } - - #endregion // Overridden methods and operators - - #region Private Helpers - private static IList ListFromSpan(TextSpan span) - { - IList list = new List(1); - list.Add(span); - return list; - } - - /// - /// Private constructor for use when the span list is already normalized. - /// - /// An already normalized span list. - private NormalizedTextSpanCollection(OrderedSpanList normalizedSpans) - : base(normalizedSpans) - { - } - - private static void UpdateSpanUnion(TextSpan span, IList spans, ref int start, ref int end) - { - if (end < span.Start) - { - spans.Add(TextSpan.FromBounds(start, end)); - - start = -1; - end = int.MaxValue; - } - - if (end == int.MaxValue) - { - start = span.Start; - end = span.End; - } - else - { - end = Math.Max(end, span.End); - } - } - - private static IList NormalizeSpans(IEnumerable spans) - { - if (spans == null) - { - throw new ArgumentNullException(nameof(spans)); - } - - List sorted = new List(spans); - if (sorted.Count <= 1) - { - return sorted; - } - else - { - sorted.Sort(delegate (TextSpan s1, TextSpan s2) { return s1.Start.CompareTo(s2.Start); }); - - IList normalized = new List(sorted.Count); - - int oldStart = sorted[0].Start; - int oldEnd = sorted[0].End; - for (int i = 1; i < sorted.Count; ++i) - { - int newStart = sorted[i].Start; - int newEnd = sorted[i].End; - if (oldEnd < newStart) - { - normalized.Add(TextSpan.FromBounds(oldStart, oldEnd)); - oldStart = newStart; - oldEnd = newEnd; - } - else - { - oldEnd = Math.Max(oldEnd, newEnd); - } - } - - normalized.Add(TextSpan.FromBounds(oldStart, oldEnd)); - return normalized; - } - } - - private class OrderedSpanList : List - { - } - - #endregion // Private Helpers - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/ObjectPool.cs b/vsintegration/src/FSharp.PatternMatcher/ObjectPool.cs deleted file mode 100644 index 72683cf8a37..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/ObjectPool.cs +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -// define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will -// make everything about 2-3x slower -// -// #define TRACE_LEAKS - -// define DETECT_LEAKS to detect possible leaks -// #if DEBUG -// #define DETECT_LEAKS //for now always enable DETECT_LEAKS in debug. -// #endif - -using System; -using System.Diagnostics; -using System.Threading; - -#if DETECT_LEAKS -using System.Runtime.CompilerServices; - -#endif -namespace Roslyn.Utilities -{ - /// - /// Generic implementation of object pooling pattern with predefined pool size limit. The main - /// purpose is that limited number of frequently used objects can be kept in the pool for - /// further recycling. - /// - /// Notes: - /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there - /// is no space in the pool, extra returned objects will be dropped. - /// - /// 2) it is implied that if object was obtained from a pool, the caller will return it back in - /// a relatively short time. Keeping checked out objects for long durations is ok, but - /// reduces usefulness of pooling. Just new up your own. - /// - /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. - /// Rationale: - /// If there is no intent for reusing the object, do not use pool - just use "new". - /// - internal class ObjectPool where T : class - { - [DebuggerDisplay("{Value,nq}")] - private struct Element - { - internal T Value; - } - - /// - /// Not using System.Func{T} because this file is linked into the (debugger) Formatter, - /// which does not have that type (since it compiles against .NET 2.0). - /// - internal delegate T Factory(); - - // Storage for the pool objects. The first item is stored in a dedicated field because we - // expect to be able to satisfy most requests from it. - private T _firstItem; - private readonly Element[] _items; - - // factory is stored for the lifetime of the pool. We will call this only when pool needs to - // expand. compared to "new T()", Func gives more flexibility to implementers and faster - // than "new T()". - private readonly Factory _factory; - -#if DETECT_LEAKS - private static readonly ConditionalWeakTable leakTrackers = new ConditionalWeakTable(); - - private class LeakTracker : IDisposable - { - private volatile bool disposed; - -#if TRACE_LEAKS - internal volatile object Trace = null; -#endif - - public void Dispose() - { - disposed = true; - GC.SuppressFinalize(this); - } - - private string GetTrace() - { -#if TRACE_LEAKS - return Trace == null ? "" : Trace.ToString(); -#else - return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n"; -#endif - } - - ~LeakTracker() - { - if (!this.disposed && !Environment.HasShutdownStarted) - { - var trace = GetTrace(); - - // If you are seeing this message it means that object has been allocated from the pool - // and has not been returned back. This is not critical, but turns pool into rather - // inefficient kind of "new". - Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END"); - } - } - } -#endif - - internal ObjectPool(Factory factory) - : this(factory, Environment.ProcessorCount * 2) - { } - - internal ObjectPool(Factory factory, int size) - { - Debug.Assert(size >= 1); - _factory = factory; - _items = new Element[size - 1]; - } - - private T CreateInstance() - { - var inst = _factory(); - return inst; - } - - /// - /// Produces an instance. - /// - /// - /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. - /// Note that Free will try to store recycled objects close to the start thus statistically - /// reducing how far we will typically search. - /// - internal T Allocate() - { - // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements. - // Note that the initial read is optimistically not synchronized. That is intentional. - // We will interlock only when we have a candidate. in a worst case we may miss some - // recently returned objects. Not a big deal. - T inst = _firstItem; - if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst)) - { - inst = AllocateSlow(); - } - -#if DETECT_LEAKS - var tracker = new LeakTracker(); - leakTrackers.Add(inst, tracker); - -#if TRACE_LEAKS - var frame = CaptureStackTrace(); - tracker.Trace = frame; -#endif -#endif - return inst; - } - - private T AllocateSlow() - { - var items = _items; - - for (int i = 0; i < items.Length; i++) - { - // Note that the initial read is optimistically not synchronized. That is intentional. - // We will interlock only when we have a candidate. in a worst case we may miss some - // recently returned objects. Not a big deal. - T inst = items[i].Value; - if (inst != null) - { - if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) - { - return inst; - } - } - } - - return CreateInstance(); - } - - /// - /// Returns objects to the pool. - /// - /// - /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. - /// Note that Free will try to store recycled objects close to the start thus statistically - /// reducing how far we will typically search in Allocate. - /// - internal void Free(T obj) - { - Validate(obj); - ForgetTrackedObject(obj); - - if (_firstItem == null) - { - // Intentionally not using interlocked here. - // In a worst case scenario two objects may be stored into same slot. - // It is very unlikely to happen and will only mean that one of the objects will get collected. - _firstItem = obj; - } - else - { - FreeSlow(obj); - } - } - - private void FreeSlow(T obj) - { - var items = _items; - for (int i = 0; i < items.Length; i++) - { - if (items[i].Value == null) - { - // Intentionally not using interlocked here. - // In a worst case scenario two objects may be stored into same slot. - // It is very unlikely to happen and will only mean that one of the objects will get collected. - items[i].Value = obj; - break; - } - } - } - - /// - /// Removes an object from leak tracking. - /// - /// This is called when an object is returned to the pool. It may also be explicitly - /// called if an object allocated from the pool is intentionally not being returned - /// to the pool. This can be of use with pooled arrays if the consumer wants to - /// return a larger array to the pool than was originally allocated. - /// - [Conditional("DEBUG")] - internal void ForgetTrackedObject(T old, T replacement = null) - { -#if DETECT_LEAKS - LeakTracker tracker; - if (leakTrackers.TryGetValue(old, out tracker)) - { - tracker.Dispose(); - leakTrackers.Remove(old); - } - else - { - var trace = CaptureStackTrace(); - Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END"); - } - - if (replacement != null) - { - tracker = new LeakTracker(); - leakTrackers.Add(replacement, tracker); - } -#endif - } - -#if DETECT_LEAKS - private static Lazy _stackTraceType = new Lazy(() => Type.GetType("System.Diagnostics.StackTrace")); - - private static object CaptureStackTrace() - { - return Activator.CreateInstance(_stackTraceType.Value); - } -#endif - - [Conditional("DEBUG")] - private void Validate(object obj) - { - Debug.Assert(obj != null, "freeing null?"); - - Debug.Assert(_firstItem != obj, "freeing twice?"); - - var items = _items; - for (int i = 0; i < items.Length; i++) - { - var value = items[i].Value; - if (value == null) - { - return; - } - - Debug.Assert(value != obj, "freeing twice?"); - } - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/ObjectReader.cs b/vsintegration/src/FSharp.PatternMatcher/ObjectReader.cs deleted file mode 100644 index e27ed515ab8..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/ObjectReader.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Roslyn.Utilities -{ - /// - /// An abstract of a stream of values that can be read from. - /// - internal abstract class ObjectReader - { - public abstract bool ReadBoolean(); - public abstract byte ReadByte(); - public abstract char ReadChar(); - public abstract decimal ReadDecimal(); - public abstract double ReadDouble(); - public abstract float ReadSingle(); - public abstract int ReadInt32(); - public abstract long ReadInt64(); - public abstract sbyte ReadSByte(); - public abstract short ReadInt16(); - public abstract uint ReadUInt32(); - public abstract ulong ReadUInt64(); - public abstract ushort ReadUInt16(); - public abstract string ReadString(); - public abstract object ReadValue(); - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/ObjectWriter.cs b/vsintegration/src/FSharp.PatternMatcher/ObjectWriter.cs deleted file mode 100644 index e7e1b290b47..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/ObjectWriter.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Microsoft.CodeAnalysis -{ - /// - /// An abstraction of a stream of values that can be written to. - /// - internal abstract class ObjectWriter - { - public abstract void WriteBoolean(bool value); - public abstract void WriteByte(byte value); - public abstract void WriteChar(char ch); - public abstract void WriteDecimal(decimal value); - public abstract void WriteDouble(double value); - public abstract void WriteSingle(float value); - public abstract void WriteInt32(int value); - public abstract void WriteInt64(long value); - public abstract void WriteSByte(sbyte value); - public abstract void WriteInt16(short value); - public abstract void WriteUInt32(uint value); - public abstract void WriteUInt64(ulong value); - public abstract void WriteUInt16(ushort value); - public abstract void WriteString(string value); - public abstract void WriteValue(object value); - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/PatternMatch.cs b/vsintegration/src/FSharp.PatternMatcher/PatternMatch.cs deleted file mode 100644 index db375590c97..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/PatternMatch.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.PatternMatching -{ - internal struct PatternMatch : IComparable - { - /// - /// The weight of a CamelCase match. A higher number indicates a more accurate match. - /// - public int? CamelCaseWeight { get; } - - /// - /// True if this was a case sensitive match. - /// - public bool IsCaseSensitive { get; } - - /// - /// The type of match that occurred. - /// - public PatternMatchKind Kind { get; } - - /// - /// The spans in the original text that were matched. Only returned if the - /// pattern matcher is asked to collect these spans. - /// - public ImmutableArray MatchedSpans { get; } - - private readonly bool _punctuationStripped; - - internal PatternMatch( - PatternMatchKind resultType, - bool punctuationStripped, - bool isCaseSensitive, - TextSpan? matchedSpan, - int? camelCaseWeight = null) - : this(resultType, punctuationStripped, isCaseSensitive, - matchedSpan == null ? ImmutableArray.Empty : ImmutableArray.Create(matchedSpan.Value), - camelCaseWeight) - { - } - - internal PatternMatch( - PatternMatchKind resultType, - bool punctuationStripped, - bool isCaseSensitive, - ImmutableArray matchedSpans, - int? camelCaseWeight = null) - : this() - { - this.Kind = resultType; - this.IsCaseSensitive = isCaseSensitive; - this.CamelCaseWeight = camelCaseWeight; - this.MatchedSpans = matchedSpans; - _punctuationStripped = punctuationStripped; - - if ((resultType == PatternMatchKind.CamelCase) != camelCaseWeight.HasValue) - { - throw new ArgumentException("A CamelCase weight must be specified if and only if the resultType is CamelCase."); - } - } - - public int CompareTo(PatternMatch other) - { - return CompareTo(other, ignoreCase: false); - } - - public int CompareTo(PatternMatch other, bool ignoreCase) - { - int diff; - if ((diff = CompareType(this, other)) != 0 || - (diff = CompareCamelCase(this, other)) != 0 || - (diff = CompareCase(this, other, ignoreCase)) != 0 || - (diff = ComparePunctuation(this, other)) != 0) - { - return diff; - } - - return 0; - } - - private static int ComparePunctuation(PatternMatch result1, PatternMatch result2) - { - // Consider a match to be better if it was successful without stripping punctuation - // versus a match that had to strip punctuation to succeed. - if (result1._punctuationStripped != result2._punctuationStripped) - { - return result1._punctuationStripped ? 1 : -1; - } - - return 0; - } - - private static int CompareCase(PatternMatch result1, PatternMatch result2, bool ignoreCase) - { - if (!ignoreCase) - { - if (result1.IsCaseSensitive != result2.IsCaseSensitive) - { - return result1.IsCaseSensitive ? -1 : 1; - } - } - - return 0; - } - - private static int CompareType(PatternMatch result1, PatternMatch result2) - { - return result1.Kind - result2.Kind; - } - - private static int CompareCamelCase(PatternMatch result1, PatternMatch result2) - { - if (result1.Kind == PatternMatchKind.CamelCase && result2.Kind == PatternMatchKind.CamelCase) - { - // Swap the values here. If result1 has a higher weight, then we want it to come - // first. - return result2.CamelCaseWeight.Value - result1.CamelCaseWeight.Value; - } - - return 0; - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/PatternMatchKind.cs b/vsintegration/src/FSharp.PatternMatcher/PatternMatchKind.cs deleted file mode 100644 index 41d0596576b..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/PatternMatchKind.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -namespace Microsoft.CodeAnalysis.PatternMatching -{ - /// - /// Note(cyrusn): this enum is ordered from strongest match type to weakest match type. - /// - internal enum PatternMatchKind - { - /// - /// The candidate string matched the pattern exactly. - /// - Exact, - - /// - /// The pattern was a prefix of the candidate string. - /// - Prefix, - - /// - /// The pattern was a substring of the candidate string, but in a way that wasn't a CamelCase match. - /// - Substring, - - /// - /// The pattern matched the CamelCased candidate string. - /// - CamelCase, - - /// - /// The pattern matches the candidate in a fuzzy manner. Fuzzy matching allows for - /// misspellings, missing words, etc. - /// - Fuzzy - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.Segment.cs b/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.Segment.cs deleted file mode 100644 index 58703925601..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.Segment.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.CodeAnalysis.PatternMatching -{ - internal partial class PatternMatcher - { - /// - /// First we break up the pattern given by dots. Each portion of the pattern between the - /// dots is a 'Segment'. The 'Segment' contains information about the entire section of - /// text between the dots, as well as information about any individual 'Words' that we - /// can break the segment into. - /// - private struct Segment : IDisposable - { - // Information about the entire piece of text between the dots. For example, if the - // text between the dots is 'GetKeyword', then TotalTextChunk.Text will be 'GetKeyword' and - // TotalTextChunk.CharacterSpans will correspond to 'G', 'et', 'K' and 'eyword'. - public readonly TextChunk TotalTextChunk; - - // Information about the subwords compromising the total word. For example, if the - // text between the dots is 'GetKeyword', then the subwords will be 'Get' and 'Keyword' - // Those individual words will have CharacterSpans of ('G' and 'et') and ('K' and 'eyword') - // respectively. - public readonly TextChunk[] SubWordTextChunks; - - public Segment(string text, bool verbatimIdentifierPrefixIsWordCharacter, bool allowFuzzyMatching) - { - this.TotalTextChunk = new TextChunk(text, allowFuzzyMatching); - this.SubWordTextChunks = BreakPatternIntoTextChunks( - text, verbatimIdentifierPrefixIsWordCharacter, allowFuzzyMatching); - } - - public void Dispose() - { - this.TotalTextChunk.Dispose(); - foreach (var chunk in this.SubWordTextChunks) - { - chunk.Dispose(); - } - } - - public bool IsInvalid => this.SubWordTextChunks.Length == 0; - - private static int CountTextChunks(string pattern, bool verbatimIdentifierPrefixIsWordCharacter) - { - int count = 0; - int wordLength = 0; - - for (int i = 0; i < pattern.Length; i++) - { - if (IsWordChar(pattern[i], verbatimIdentifierPrefixIsWordCharacter)) - { - wordLength++; - } - else - { - if (wordLength > 0) - { - count++; - wordLength = 0; - } - } - } - - if (wordLength > 0) - { - count++; - } - - return count; - } - - private static TextChunk[] _emptyTextChunkArray = new TextChunk[0]; - - private static TextChunk[] BreakPatternIntoTextChunks( - string pattern, bool verbatimIdentifierPrefixIsWordCharacter, bool allowFuzzyMatching) - { - int partCount = CountTextChunks(pattern, verbatimIdentifierPrefixIsWordCharacter); - - if (partCount == 0) - { - return _emptyTextChunkArray; - } - - var result = new TextChunk[partCount]; - int resultIndex = 0; - int wordStart = 0; - int wordLength = 0; - - for (int i = 0; i < pattern.Length; i++) - { - var ch = pattern[i]; - if (IsWordChar(ch, verbatimIdentifierPrefixIsWordCharacter)) - { - if (wordLength++ == 0) - { - wordStart = i; - } - } - else - { - if (wordLength > 0) - { - result[resultIndex++] = new TextChunk(pattern.Substring(wordStart, wordLength), allowFuzzyMatching); - wordLength = 0; - } - } - } - - if (wordLength > 0) - { - result[resultIndex++] = new TextChunk(pattern.Substring(wordStart, wordLength), allowFuzzyMatching); - } - - return result; - } - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.TextChunk.cs b/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.TextChunk.cs deleted file mode 100644 index 55499bf43d6..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.TextChunk.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.PatternMatching -{ - internal partial class PatternMatcher - { - /// - /// Information about a chunk of text from the pattern. The chunk is a piece of text, with - /// cached information about the character spans within in. Character spans separate out - /// capitalized runs and lowercase runs. i.e. if you have AAbb, then there will be two - /// character spans, one for AA and one for BB. - /// - private struct TextChunk : IDisposable - { - public readonly string Text; - - /// - /// Character spans separate out - /// capitalized runs and lowercase runs. i.e. if you have AAbb, then there will be two - /// character spans, one for AA and one for BB. - /// - public readonly StringBreaks CharacterSpans; - - public readonly WordSimilarityChecker SimilarityChecker; - - public TextChunk(string text, bool allowFuzzingMatching) - { - this.Text = text; - this.CharacterSpans = StringBreaker.BreakIntoCharacterParts(text); - this.SimilarityChecker = allowFuzzingMatching - ? new WordSimilarityChecker(text, substringsAreSimilar: false) - : null; - } - - public void Dispose() - { - this.SimilarityChecker?.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.cs b/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.cs deleted file mode 100644 index a538c9a13ce..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/PatternMatcher.cs +++ /dev/null @@ -1,737 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.Contracts; -using System.Globalization; -using System.Linq; -using Microsoft.CodeAnalysis.Shared; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.PatternMatching -{ - /// - /// The pattern matcher is thread-safe. However, it maintains an internal cache of - /// information as it is used. Therefore, you should not keep it around forever and should get - /// and release the matcher appropriately once you no longer need it. - /// Also, while the pattern matcher is culture aware, it uses the culture specified in the - /// constructor. - /// - internal sealed partial class PatternMatcher : IDisposable - { - private static readonly char[] s_dotCharacterArray = { '.' }; - - private readonly object _gate = new object(); - - private readonly bool _allowFuzzyMatching; - private readonly bool _invalidPattern; - private readonly Segment _fullPatternSegment; - private readonly Segment[] _dotSeparatedSegments; - - private readonly Dictionary _stringToWordSpans = new Dictionary(); - private readonly Func _breakIntoWordSpans = StringBreaker.BreakIntoWordParts; - private static Segment[] _emptySegmentArray = new Segment[0]; - - // PERF: Cache the culture's compareInfo to avoid the overhead of asking for them repeatedly in inner loops - private readonly CompareInfo _compareInfo; - - /// - /// Construct a new PatternMatcher using the calling thread's culture for string searching and comparison. - /// - public PatternMatcher( - string pattern, - bool verbatimIdentifierPrefixIsWordCharacter = false, - bool allowFuzzyMatching = false) : - this(pattern, CultureInfo.CurrentCulture, verbatimIdentifierPrefixIsWordCharacter, allowFuzzyMatching) - { - } - - /// - /// Construct a new PatternMatcher using the specified culture. - /// - /// The pattern to make the pattern matcher for. - /// The culture to use for string searching and comparison. - /// Whether to consider "@" as a word character - /// Whether or not close matches should count as matches. - public PatternMatcher( - string pattern, - CultureInfo culture, - bool verbatimIdentifierPrefixIsWordCharacter, - bool allowFuzzyMatching) - { - pattern = pattern.Trim(); - _compareInfo = culture.CompareInfo; - _allowFuzzyMatching = allowFuzzyMatching; - - _fullPatternSegment = new Segment(pattern, verbatimIdentifierPrefixIsWordCharacter, allowFuzzyMatching); - - if (pattern.IndexOf('.') < 0) - { - // PERF: Avoid string.Split allocations when the pattern doesn't contain a dot. - _dotSeparatedSegments = pattern.Length > 0 - ? new Segment[1] { _fullPatternSegment } - : _emptySegmentArray; - } - else - { - _dotSeparatedSegments = pattern.Split(s_dotCharacterArray, StringSplitOptions.RemoveEmptyEntries) - .Select(text => new Segment(text.Trim(), verbatimIdentifierPrefixIsWordCharacter, allowFuzzyMatching)) - .ToArray(); - } - - _invalidPattern = _dotSeparatedSegments.Length == 0 || _dotSeparatedSegments.Any(s => s.IsInvalid); - } - - public void Dispose() - { - _fullPatternSegment.Dispose(); - foreach (var segment in _dotSeparatedSegments) - { - segment.Dispose(); - } - } - - public bool IsDottedPattern => _dotSeparatedSegments.Length > 1; - - private bool SkipMatch(string candidate) - { - return _invalidPattern || string.IsNullOrWhiteSpace(candidate); - } - - public ImmutableArray GetMatches(string candidate) - { - return GetMatches(candidate, includeMatchSpans: false); - } - - /// - /// Determines if a given candidate string matches under a multiple word query text, as you - /// would find in features like Navigate To. - /// - /// The word being tested. - /// Whether or not the matched spans should be included with results - /// If this was a match, a set of match types that occurred while matching the - /// patterns. If it was not a match, it returns null. - public ImmutableArray GetMatches(string candidate, bool includeMatchSpans) - { - if (SkipMatch(candidate)) - { - return ImmutableArray.Empty; - } - - var result = MatchSegment(candidate, includeMatchSpans, _fullPatternSegment, fuzzyMatch: true); - if (!result.IsEmpty) - { - return result; - } - - return MatchSegment(candidate, includeMatchSpans, _fullPatternSegment, fuzzyMatch: false); - } - - public ImmutableArray GetMatchesForLastSegmentOfPattern(string candidate) - { - if (SkipMatch(candidate)) - { - return ImmutableArray.Empty; - } - - var result = MatchSegment(candidate, includeMatchSpans: false, segment: _dotSeparatedSegments.Last(), fuzzyMatch: false); - if (!result.IsEmpty) - { - return result; - } - - return MatchSegment(candidate, includeMatchSpans: false, segment: _dotSeparatedSegments.Last(), fuzzyMatch: true); - } - - public PatternMatches GetMatches(string candidate, string dottedContainer) - { - return GetMatches(candidate, dottedContainer, includeMatchSpans: false); - } - - /// - /// Matches a pattern against a candidate, and an optional dotted container for the - /// candidate. If the container is provided, and the pattern itself contains dots, then - /// the pattern will be tested against the candidate and container. Specifically, - /// the part of the pattern after the last dot will be tested against the candidate. If - /// a match occurs there, then the remaining dot-separated portions of the pattern will - /// be tested against every successive portion of the container from right to left. - /// - /// i.e. if you have a pattern of "Con.WL" and the candidate is "WriteLine" with a - /// dotted container of "System.Console", then "WL" will be tested against "WriteLine". - /// With a match found there, "Con" will then be tested against "Console". - /// - public PatternMatches GetMatches( - string candidate, string dottedContainer, bool includeMatchSpans) - { - var result = GetMatches(candidate, dottedContainer, includeMatchSpans, fuzzyMatch: false); - if (!result.IsEmpty) - { - return result; - } - - return GetMatches(candidate, dottedContainer, includeMatchSpans, fuzzyMatch: true); - } - - private PatternMatches GetMatches( - string candidate, string dottedContainer, bool includeMatchSpans, bool fuzzyMatch) - { - if (fuzzyMatch && !_allowFuzzyMatching) - { - return PatternMatches.Empty; - } - - if (SkipMatch(candidate)) - { - return PatternMatches.Empty; - } - - // First, check that the last part of the dot separated pattern matches the name of the - // candidate. If not, then there's no point in proceeding and doing the more - // expensive work. - var candidateMatch = MatchSegment(candidate, includeMatchSpans, _dotSeparatedSegments.Last(), fuzzyMatch); - if (candidateMatch.IsDefaultOrEmpty) - { - return PatternMatches.Empty; - } - - dottedContainer = dottedContainer ?? string.Empty; - var containerParts = dottedContainer.Split(s_dotCharacterArray, StringSplitOptions.RemoveEmptyEntries); - - // -1 because the last part was checked against the name, and only the rest - // of the parts are checked against the container. - var relevantDotSeparatedSegmentLength = _dotSeparatedSegments.Length - 1; - if (relevantDotSeparatedSegmentLength > containerParts.Length) - { - // There weren't enough container parts to match against the pattern parts. - // So this definitely doesn't match. - return PatternMatches.Empty; - } - - // So far so good. Now break up the container for the candidate and check if all - // the dotted parts match up correctly. - var containerMatches = ArrayBuilder.GetInstance(); - - try - { - // Don't need to check the last segment. We did that as the very first bail out step. - for (int i = 0, j = containerParts.Length - relevantDotSeparatedSegmentLength; - i < relevantDotSeparatedSegmentLength; - i++, j++) - { - var segment = _dotSeparatedSegments[i]; - var containerName = containerParts[j]; - var containerMatch = MatchSegment(containerName, includeMatchSpans, segment, fuzzyMatch); - if (containerMatch.IsDefaultOrEmpty) - { - // This container didn't match the pattern piece. So there's no match at all. - return PatternMatches.Empty; - } - - containerMatches.AddRange(containerMatch); - } - - // Success, this symbol's full name matched against the dotted name the user was asking - // about. - return new PatternMatches(candidateMatch, containerMatches.ToImmutable()); - } - finally - { - containerMatches.Free(); - } - } - - /// - /// Determines if a given candidate string matches under a multiple word query text, as you - /// would find in features like Navigate To. - /// - /// - /// PERF: This is slightly faster and uses less memory than - /// so, unless you need to know the full set of matches, use this version. - /// - /// The word being tested. - /// Whether or not the matched spans should be included with results - /// If this was a match, the first element of the set of match types that occurred while matching the - /// patterns. If it was not a match, it returns null. - public PatternMatch? GetFirstMatch(string candidate, bool inludeMatchSpans = false) - { - if (SkipMatch(candidate)) - { - return null; - } - - ImmutableArray ignored; - var result = MatchSegment(candidate, inludeMatchSpans, _fullPatternSegment, wantAllMatches: false, allMatches: out ignored, fuzzyMatch: false); - return result != null ? result : MatchSegment(candidate, inludeMatchSpans, _fullPatternSegment, wantAllMatches: false, allMatches: out ignored, fuzzyMatch: true); - } - - private StringBreaks GetWordSpans(string word) - { - lock (_gate) - { - return _stringToWordSpans.GetOrAdd(word, _breakIntoWordSpans); - } - } - - internal PatternMatch? MatchSingleWordPattern_ForTestingOnly(string candidate) - { - return MatchTextChunk(candidate, includeMatchSpans: true, - chunk: _fullPatternSegment.TotalTextChunk, punctuationStripped: false, - fuzzyMatch: false); - } - - private static bool ContainsUpperCaseLetter(string pattern) - { - // Expansion of "foreach(char ch in pattern)" to avoid a CharEnumerator allocation - for (int i = 0; i < pattern.Length; i++) - { - if (char.IsUpper(pattern[i])) - { - return true; - } - } - - return false; - } - - private PatternMatch? MatchTextChunk( - string candidate, - bool includeMatchSpans, - TextChunk chunk, - bool punctuationStripped, - bool fuzzyMatch) - { - int caseInsensitiveIndex = _compareInfo.IndexOf(candidate, chunk.Text, CompareOptions.IgnoreCase); - if (caseInsensitiveIndex == 0) - { - if (chunk.Text.Length == candidate.Length) - { - // a) Check if the part matches the candidate entirely, in an case insensitive or - // sensitive manner. If it does, return that there was an exact match. - return new PatternMatch( - PatternMatchKind.Exact, punctuationStripped, isCaseSensitive: candidate == chunk.Text, - matchedSpan: GetMatchedSpan(includeMatchSpans, 0, candidate.Length)); - } - else - { - // b) Check if the part is a prefix of the candidate, in a case insensitive or sensitive - // manner. If it does, return that there was a prefix match. - return new PatternMatch( - PatternMatchKind.Prefix, punctuationStripped, isCaseSensitive: _compareInfo.IsPrefix(candidate, chunk.Text), - matchedSpan: GetMatchedSpan(includeMatchSpans, 0, chunk.Text.Length)); - } - } - - var isLowercase = !ContainsUpperCaseLetter(chunk.Text); - if (isLowercase) - { - if (caseInsensitiveIndex > 0) - { - // c) If the part is entirely lowercase, then check if it is contained anywhere in the - // candidate in a case insensitive manner. If so, return that there was a substring - // match. - // - // Note: We only have a substring match if the lowercase part is prefix match of some - // word part. That way we don't match something like 'Class' when the user types 'a'. - // But we would match 'FooAttribute' (since 'Attribute' starts with 'a'). - var wordSpans = GetWordSpans(candidate); - for (int i = 0; i < wordSpans.Count; i++) - { - var span = wordSpans[i]; - if (PartStartsWith(candidate, span, chunk.Text, CompareOptions.IgnoreCase)) - { - return new PatternMatch(PatternMatchKind.Substring, punctuationStripped, - isCaseSensitive: PartStartsWith(candidate, span, chunk.Text, CompareOptions.None), - matchedSpan: GetMatchedSpan(includeMatchSpans, span.Start, chunk.Text.Length)); - } - } - } - } - else - { - // d) If the part was not entirely lowercase, then check if it is contained in the - // candidate in a case *sensitive* manner. If so, return that there was a substring - // match. - var caseSensitiveIndex = _compareInfo.IndexOf(candidate, chunk.Text); - if (caseSensitiveIndex > 0) - { - return new PatternMatch( - PatternMatchKind.Substring, punctuationStripped, isCaseSensitive: true, - matchedSpan: GetMatchedSpan(includeMatchSpans, caseSensitiveIndex, chunk.Text.Length)); - } - } - - if (!isLowercase) - { - // e) If the part was not entirely lowercase, then attempt a camel cased match as well. - if (chunk.CharacterSpans.Count > 0) - { - var candidateParts = GetWordSpans(candidate); - List matchedSpans; - var camelCaseWeight = TryCamelCaseMatch(candidate, includeMatchSpans, candidateParts, chunk, CompareOptions.None, out matchedSpans); - if (camelCaseWeight.HasValue) - { - return new PatternMatch( - PatternMatchKind.CamelCase, punctuationStripped, isCaseSensitive: true, camelCaseWeight: camelCaseWeight, - matchedSpans: GetMatchedSpans(includeMatchSpans, matchedSpans)); - } - - camelCaseWeight = TryCamelCaseMatch(candidate, includeMatchSpans, candidateParts, chunk, CompareOptions.IgnoreCase, out matchedSpans); - if (camelCaseWeight.HasValue) - { - return new PatternMatch( - PatternMatchKind.CamelCase, punctuationStripped, isCaseSensitive: false, camelCaseWeight: camelCaseWeight, - matchedSpans: GetMatchedSpans(includeMatchSpans, matchedSpans)); - } - } - } - - if (isLowercase) - { - // f) Is the pattern a substring of the candidate starting on one of the candidate's word boundaries? - - // We could check every character boundary start of the candidate for the pattern. However, that's - // an m * n operation in the worst case. Instead, find the first instance of the pattern - // substring, and see if it starts on a capital letter. It seems unlikely that the user will try to - // filter the list based on a substring that starts on a capital letter and also with a lowercase one. - // (Pattern: fogbar, Candidate: quuxfogbarFogBar). - if (chunk.Text.Length < candidate.Length) - { - if (caseInsensitiveIndex != -1 && char.IsUpper(candidate[caseInsensitiveIndex])) - { - return new PatternMatch( - PatternMatchKind.Substring, punctuationStripped, isCaseSensitive: false, - matchedSpan: GetMatchedSpan(includeMatchSpans, caseInsensitiveIndex, chunk.Text.Length)); - } - } - } - - if (fuzzyMatch) - { - if (chunk.SimilarityChecker.AreSimilar(candidate)) - { - return new PatternMatch( - PatternMatchKind.Fuzzy, punctuationStripped, isCaseSensitive: false, matchedSpan: null); - } - } - - return null; - } - - private ImmutableArray GetMatchedSpans(bool includeMatchSpans, List matchedSpans) - { - return includeMatchSpans - ? new NormalizedTextSpanCollection(matchedSpans).ToImmutableArray() - : ImmutableArray.Empty; - } - - private static TextSpan? GetMatchedSpan(bool includeMatchSpans, int start, int length) - { - return includeMatchSpans ? new TextSpan(start, length) : (TextSpan?)null; - } - - private static bool ContainsSpaceOrAsterisk(string text) - { - for (int i = 0; i < text.Length; i++) - { - char ch = text[i]; - if (ch == ' ' || ch == '*') - { - return true; - } - } - - return false; - } - - private ImmutableArray MatchSegment( - string candidate, bool includeMatchSpans, Segment segment, bool fuzzyMatch) - { - if (fuzzyMatch && !_allowFuzzyMatching) - { - return ImmutableArray.Empty; - } - - ImmutableArray matches; - var singleMatch = MatchSegment(candidate, includeMatchSpans, segment, - wantAllMatches: true, fuzzyMatch: fuzzyMatch, allMatches: out matches); - if (singleMatch.HasValue) - { - return ImmutableArray.Create(singleMatch.Value); - } - - return matches; - } - - /// - /// Internal helper for MatchPatternInternal - /// - /// - /// PERF: Designed to minimize allocations in common cases. - /// If there's no match, then null is returned. - /// If there's a single match, or the caller only wants the first match, then it is returned (as a Nullable) - /// If there are multiple matches, and the caller wants them all, then a List is allocated. - /// - /// The word being tested. - /// The segment of the pattern to check against the candidate. - /// Does the caller want all matches or just the first? - /// If a fuzzy match should be performed - /// If is true, and there's more than one match, then the list of all matches. - /// Whether or not the matched spans should be included with results - /// If there's only one match, then the return value is that match. Otherwise it is null. - private PatternMatch? MatchSegment( - string candidate, - bool includeMatchSpans, - Segment segment, - bool wantAllMatches, - bool fuzzyMatch, - out ImmutableArray allMatches) - { - allMatches = ImmutableArray.Empty; - - if (fuzzyMatch && !_allowFuzzyMatching) - { - return null; - } - - // First check if the segment matches as is. This is also useful if the segment contains - // characters we would normally strip when splitting into parts that we also may want to - // match in the candidate. For example if the segment is "@int" and the candidate is - // "@int", then that will show up as an exact match here. - // - // Note: if the segment contains a space or an asterisk then we must assume that it's a - // multi-word segment. - if (!ContainsSpaceOrAsterisk(segment.TotalTextChunk.Text)) - { - var match = MatchTextChunk(candidate, includeMatchSpans, - segment.TotalTextChunk, punctuationStripped: false, fuzzyMatch: fuzzyMatch); - if (match != null) - { - return match; - } - } - - // The logic for pattern matching is now as follows: - // - // 1) Break the segment passed in into words. Breaking is rather simple and a - // good way to think about it that if gives you all the individual alphanumeric words - // of the pattern. - // - // 2) For each word try to match the word against the candidate value. - // - // 3) Matching is as follows: - // - // a) Check if the word matches the candidate entirely, in an case insensitive or - // sensitive manner. If it does, return that there was an exact match. - // - // b) Check if the word is a prefix of the candidate, in a case insensitive or - // sensitive manner. If it does, return that there was a prefix match. - // - // c) If the word is entirely lowercase, then check if it is contained anywhere in the - // candidate in a case insensitive manner. If so, return that there was a substring - // match. - // - // Note: We only have a substring match if the lowercase part is prefix match of - // some word part. That way we don't match something like 'Class' when the user - // types 'a'. But we would match 'FooAttribute' (since 'Attribute' starts with - // 'a'). - // - // d) If the word was not entirely lowercase, then check if it is contained in the - // candidate in a case *sensitive* manner. If so, return that there was a substring - // match. - // - // e) If the word was not entirely lowercase, then attempt a camel cased match as - // well. - // - // f) The word is all lower case. Is it a case insensitive substring of the candidate starting - // on a part boundary of the candidate? - // - // Only if all words have some sort of match is the pattern considered matched. - - var subWordTextChunks = segment.SubWordTextChunks; - var matches = ArrayBuilder.GetInstance(); - - try - { - foreach (var subWordTextChunk in subWordTextChunks) - { - // Try to match the candidate with this word - var result = MatchTextChunk(candidate, includeMatchSpans, - subWordTextChunk, punctuationStripped: true, fuzzyMatch: fuzzyMatch); - if (result == null) - { - return null; - } - - if (!wantAllMatches || subWordTextChunks.Length == 1) - { - // Stop at the first word - return result; - } - - matches.Add(result.Value); - } - - allMatches = matches.ToImmutable(); - return null; - } - finally - { - matches.Free(); - } - } - - private static bool IsWordChar(char ch, bool verbatimIdentifierPrefixIsWordCharacter) - { - return char.IsLetterOrDigit(ch) || ch == '_' || (verbatimIdentifierPrefixIsWordCharacter && ch == '@'); - } - - /// - /// Do the two 'parts' match? i.e. Does the candidate part start with the pattern part? - /// - /// The candidate text - /// The span within the text - /// The pattern text - /// The span within the text - /// Options for doing the comparison (case sensitive or not) - /// True if the span identified by within starts with - /// the span identified by within . - private bool PartStartsWith(string candidate, TextSpan candidatePart, string pattern, TextSpan patternPart, CompareOptions compareOptions) - { - if (patternPart.Length > candidatePart.Length) - { - // Pattern part is longer than the candidate part. There can never be a match. - return false; - } - - return _compareInfo.Compare(candidate, candidatePart.Start, patternPart.Length, pattern, patternPart.Start, patternPart.Length, compareOptions) == 0; - } - - /// - /// Does the given part start with the given pattern? - /// - /// The candidate text - /// The span within the text - /// The pattern text - /// Options for doing the comparison (case sensitive or not) - /// True if the span identified by within starts with - private bool PartStartsWith(string candidate, TextSpan candidatePart, string pattern, CompareOptions compareOptions) - { - return PartStartsWith(candidate, candidatePart, pattern, new TextSpan(0, pattern.Length), compareOptions); - } - - private int? TryCamelCaseMatch( - string candidate, - bool includeMatchedSpans, - StringBreaks candidateParts, - TextChunk chunk, - CompareOptions compareOption, - out List matchedSpans) - { - matchedSpans = null; - var chunkCharacterSpans = chunk.CharacterSpans; - - // Note: we may have more pattern parts than candidate parts. This is because multiple - // pattern parts may match a candidate part. For example "SiUI" against "SimpleUI". - // We'll have 3 pattern parts Si/U/I against two candidate parts Simple/UI. However, U - // and I will both match in UI. - - int currentCandidate = 0; - int currentChunkSpan = 0; - int? firstMatch = null; - bool? contiguous = null; - - while (true) - { - // Let's consider our termination cases - if (currentChunkSpan == chunkCharacterSpans.Count) - { - Contract.Requires(firstMatch.HasValue); - Contract.Requires(contiguous.HasValue); - - // We did match! We shall assign a weight to this - int weight = 0; - - // Was this contiguous? - if (contiguous.Value) - { - weight += 1; - } - - // Did we start at the beginning of the candidate? - if (firstMatch.Value == 0) - { - weight += 2; - } - - return weight; - } - else if (currentCandidate == candidateParts.Count) - { - // No match, since we still have more of the pattern to hit - matchedSpans = null; - return null; - } - - var candidatePart = candidateParts[currentCandidate]; - bool gotOneMatchThisCandidate = false; - - // Consider the case of matching SiUI against SimpleUIElement. The candidate parts - // will be Simple/UI/Element, and the pattern parts will be Si/U/I. We'll match 'Si' - // against 'Simple' first. Then we'll match 'U' against 'UI'. However, we want to - // still keep matching pattern parts against that candidate part. - for (; currentChunkSpan < chunkCharacterSpans.Count; currentChunkSpan++) - { - var chunkCharacterSpan = chunkCharacterSpans[currentChunkSpan]; - - if (gotOneMatchThisCandidate) - { - // We've already gotten one pattern part match in this candidate. We will - // only continue trying to consume pattern parts if the last part and this - // part are both upper case. - if (!char.IsUpper(chunk.Text[chunkCharacterSpans[currentChunkSpan - 1].Start]) || - !char.IsUpper(chunk.Text[chunkCharacterSpans[currentChunkSpan].Start])) - { - break; - } - } - - if (!PartStartsWith(candidate, candidatePart, chunk.Text, chunkCharacterSpan, compareOption)) - { - break; - } - - if (includeMatchedSpans) - { - matchedSpans = matchedSpans ?? new List(); - matchedSpans.Add(new TextSpan(candidatePart.Start, chunkCharacterSpan.Length)); - } - - gotOneMatchThisCandidate = true; - - firstMatch = firstMatch ?? currentCandidate; - - // If we were contiguous, then keep that value. If we weren't, then keep that - // value. If we don't know, then set the value to 'true' as an initial match is - // obviously contiguous. - contiguous = contiguous ?? true; - - candidatePart = new TextSpan(candidatePart.Start + chunkCharacterSpan.Length, candidatePart.Length - chunkCharacterSpan.Length); - } - - // Check if we matched anything at all. If we didn't, then we need to unset the - // contiguous bit if we currently had it set. - // If we haven't set the bit yet, then that means we haven't matched anything so - // far, and we don't want to change that. - if (!gotOneMatchThisCandidate && contiguous.HasValue) - { - contiguous = false; - } - - // Move onto the next candidate. - currentCandidate++; - } - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/PatternMatches.cs b/vsintegration/src/FSharp.PatternMatcher/PatternMatches.cs deleted file mode 100644 index a73ea5d0084..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/PatternMatches.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Linq; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.PatternMatching -{ - /// - /// Pattern matching results returned when calling - /// Specifically, this type individually provides the matches produced when matching against the - /// 'candidate' text and the 'container' text. - /// - internal struct PatternMatches - { - public static readonly PatternMatches Empty = new PatternMatches( - ImmutableArray.Empty, ImmutableArray.Empty); - - public readonly ImmutableArray CandidateMatches; - public readonly ImmutableArray ContainerMatches; - - public PatternMatches(ImmutableArray candidateMatches, - ImmutableArray containerMatches = default(ImmutableArray)) - { - CandidateMatches = candidateMatches.NullToEmpty(); - ContainerMatches = containerMatches.NullToEmpty(); - } - - public bool IsEmpty => CandidateMatches.IsEmpty && ContainerMatches.IsEmpty; - - internal bool All(Func predicate) - { - return CandidateMatches.All(predicate) && ContainerMatches.All(predicate); - } - - internal bool Any(Func predicate) - { - return CandidateMatches.Any(predicate) || ContainerMatches.Any(predicate); - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/PooledHashSet.cs b/vsintegration/src/FSharp.PatternMatcher/PooledHashSet.cs deleted file mode 100644 index fd11c411eb8..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/PooledHashSet.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Diagnostics; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Collections -{ - // HashSet that can be recycled via an object pool - // NOTE: these HashSets always have the default comparer. - internal class PooledHashSet : HashSet - { - private readonly ObjectPool> _pool; - - private PooledHashSet(ObjectPool> pool) - { - _pool = pool; - } - - public void Free() - { - this.Clear(); - _pool?.Free(this); - } - - // global pool - private static readonly ObjectPool> s_poolInstance = CreatePool(); - - // if someone needs to create a pool; - public static ObjectPool> CreatePool() - { - ObjectPool> pool = null; - pool = new ObjectPool>(() => new PooledHashSet(pool), 128); - return pool; - } - - public static PooledHashSet GetInstance() - { - var instance = s_poolInstance.Allocate(); - Debug.Assert(instance.Count == 0); - return instance; - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/PooledStringBuilder.cs b/vsintegration/src/FSharp.PatternMatcher/PooledStringBuilder.cs deleted file mode 100644 index 90226ee6109..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/PooledStringBuilder.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Roslyn.Utilities; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace System.Reflection.Internal -{ - internal class PooledStringBuilder - { - public readonly StringBuilder Builder = new StringBuilder(); - private readonly ObjectPool _pool; - - private PooledStringBuilder(ObjectPool pool) - { - Debug.Assert(pool != null); - _pool = pool; - } - - public int Length - { - get { return this.Builder.Length; } - } - - public void Free() - { - var builder = this.Builder; - - // do not store builders that are too large. - if (builder.Capacity <= 1024) - { - builder.Clear(); - _pool.Free(this); - } - } - - public string ToStringAndFree() - { - string result = this.Builder.ToString(); - this.Free(); - - return result; - } - - // global pool - private static readonly ObjectPool s_poolInstance = CreatePool(); - - // if someone needs to create a private pool; - public static ObjectPool CreatePool() - { - ObjectPool pool = null; - pool = new ObjectPool(() => new PooledStringBuilder(pool), 32); - return pool; - } - - public static PooledStringBuilder GetInstance() - { - var builder = s_poolInstance.Allocate(); - Debug.Assert(builder.Builder.Length == 0); - return builder; - } - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/Properties/AssemblyInfo.cs b/vsintegration/src/FSharp.PatternMatcher/Properties/AssemblyInfo.cs deleted file mode 100644 index c16abf8279f..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - diff --git a/vsintegration/src/FSharp.PatternMatcher/SpellChecker.cs b/vsintegration/src/FSharp.PatternMatcher/SpellChecker.cs deleted file mode 100644 index 3021df5795b..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/SpellChecker.cs +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Utilities; - -namespace Roslyn.Utilities -{ - internal class SpellChecker - { - private const string SerializationFormat = "2"; - - public VersionStamp Version { get; } - private readonly BKTree _bkTree; - - public SpellChecker(VersionStamp version, BKTree bKTree) - { - Version = version; - _bkTree = bKTree; - } - - public SpellChecker(VersionStamp version, IEnumerable corpus) - : this(version, BKTree.Create(corpus)) - { - } - - public IList FindSimilarWords(string value) - { - return FindSimilarWords(value, substringsAreSimilar: false); - } - - public IList FindSimilarWords(string value, bool substringsAreSimilar) - { - var result = _bkTree.Find(value, threshold: null); - - using (var spellChecker = new WordSimilarityChecker(value, substringsAreSimilar)) - { - return result.Where(spellChecker.AreSimilar).ToArray(); - } - } - -#if false - internal void WriteTo(ObjectWriter writer) - { - writer.WriteString(SerializationFormat); - Version.WriteTo(writer); - _bkTree.WriteTo(writer); - } - - internal static SpellChecker ReadFrom(ObjectReader reader) - { - try - { - var formatVersion = reader.ReadString(); - if (string.Equals(formatVersion, SerializationFormat, StringComparison.Ordinal)) - { - var version = VersionStamp.ReadFrom(reader); - var bkTree = BKTree.ReadFrom(reader); - if (bkTree != null) - { - return new SpellChecker(version, bkTree); - } - } - } - catch - { - Logger.Log(FunctionId.SpellChecker_ExceptionInCacheRead); - } - - return null; - } -#endif - } - - internal class WordSimilarityChecker : IDisposable - { - private struct CacheResult - { - public readonly string CandidateText; - public readonly bool AreSimilar; - public readonly double SimilarityWeight; - - public CacheResult(string candidate, bool areSimilar, double similarityWeight) - { - CandidateText = candidate; - AreSimilar = areSimilar; - SimilarityWeight = similarityWeight; - } - } - - // Cache the result of the last call to AreSimilar. We'll often be called with the same - // value multiple times in a row, so we can avoid expensive computation by returning the - // same value immediately. - private CacheResult _lastAreSimilarResult; - - private string _source; - private EditDistance _editDistance; - private readonly int _threshold; - - /// - /// Whether or words should be considered similar if one is contained within the other - /// (regardless of edit distance). For example if is true then IService would be considered - /// similar to IServiceFactory despite the edit distance being quite high at 7. - /// - private readonly bool _substringsAreSimilar; - - public WordSimilarityChecker(string text, bool substringsAreSimilar) - { - if (text != null) - _source = text; - else - throw new ArgumentNullException("text"); - - _threshold = GetThreshold(_source); - _editDistance = new EditDistance(text); - _substringsAreSimilar = substringsAreSimilar; - } - - public void Dispose() - { - _editDistance?.Dispose(); - _editDistance = null; - } - - public static bool AreSimilar(string originalText, string candidateText) - { - return AreSimilar(originalText, candidateText, substringsAreSimilar: false); - } - - public static bool AreSimilar(string originalText, string candidateText, bool substringsAreSimilar) - { - double unused; - return AreSimilar(originalText, candidateText, substringsAreSimilar, out unused); - } - - public static bool AreSimilar(string originalText, string candidateText, out double similarityWeight) - { - return AreSimilar( - originalText, candidateText, - substringsAreSimilar: false, similarityWeight: out similarityWeight); - } - - /// - /// Returns true if 'originalText' and 'candidateText' are likely a misspelling of each other. - /// Returns false otherwise. If it is a likely misspelling a similarityWeight is provided - /// to help rank the match. Lower costs mean it was a better match. - /// - public static bool AreSimilar(string originalText, string candidateText, bool substringsAreSimilar, out double similarityWeight) - { - using (var checker = new WordSimilarityChecker(originalText, substringsAreSimilar)) - { - return checker.AreSimilar(candidateText, out similarityWeight); - } - } - - internal static int GetThreshold(string value) - { - return value.Length <= 4 ? 1 : 2; - } - - public bool AreSimilar(string candidateText) - { - double similarityWeight; - return AreSimilar(candidateText, out similarityWeight); - } - - public bool AreSimilar(string candidateText, out double similarityWeight) - { - if (_source.Length < 3) - { - // If we're comparing strings that are too short, we'll find - // far too many spurious hits. Don't even bother in this case. - similarityWeight = double.MaxValue; - return false; - } - - if (_lastAreSimilarResult.CandidateText == candidateText) - { - similarityWeight = _lastAreSimilarResult.SimilarityWeight; - return _lastAreSimilarResult.AreSimilar; - } - - var result = AreSimilarWorker(candidateText, out similarityWeight); - _lastAreSimilarResult = new CacheResult(candidateText, result, similarityWeight); - return result; - } - - private bool AreSimilarWorker(string candidateText, out double similarityWeight) - { - similarityWeight = double.MaxValue; - - // If the two strings differ by more characters than the cost threshold, then there's - // no point in even computing the edit distance as it would necessarily take at least - // that many additions/deletions. - if (Math.Abs(_source.Length - candidateText.Length) <= _threshold) - { - similarityWeight = _editDistance.GetEditDistance(candidateText, _threshold); - } - - if (similarityWeight > _threshold) - { - // it had a high cost. However, the string the user typed was contained - // in the string we're currently looking at. That's enough to consider it - // although we place it just at the threshold (i.e. it's worse than all - // other matches). - if (_substringsAreSimilar && candidateText.IndexOf(_source, StringComparison.OrdinalIgnoreCase) >= 0) - { - similarityWeight = _threshold; - } - else - { - return false; - } - } - - Debug.Assert(similarityWeight <= _threshold); - - similarityWeight += Penalty(candidateText, _source); - return true; - } - - private static double Penalty(string candidateText, string originalText) - { - int lengthDifference = Math.Abs(originalText.Length - candidateText.Length); - if (lengthDifference != 0) - { - // For all items of the same edit cost, we penalize those that are - // much longer than the original text versus those that are only - // a little longer. - // - // Note: even with this penalty, all matches of cost 'X' will all still - // cost less than matches of cost 'X + 1'. i.e. the penalty is in the - // range [0, 1) and only serves to order matches of the same cost. - // - // Here's the relation of the first few values of length diff and penalty: - // LengthDiff -> Penalty - // 1 -> .5 - // 2 -> .66 - // 3 -> .75 - // 4 -> .8 - // And so on and so forth. - double penalty = 1.0 - (1.0 / (lengthDifference + 1)); - return penalty; - } - - return 0; - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/StringBreaker.cs b/vsintegration/src/FSharp.PatternMatcher/StringBreaker.cs deleted file mode 100644 index 1282f7d153d..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/StringBreaker.cs +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using Roslyn.Utilities; -using System; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Microsoft.CodeAnalysis.Shared.Utilities -{ - /// - /// Values returned from routines. - /// Optimized for short strings with a handful of spans. - /// Each span is encoded in two bitfields 'gap' and 'length' and these - /// bitfields are stored in a 32-bit bitmap. - /// Falls back to a if the encoding won't work. - /// - internal struct StringBreaks - { - private readonly List _spans; - private readonly EncodedSpans _encodedSpans; - - // These two values may be adjusted. The remaining constants are - // derived from them. The values are chosen to minimize the number - // of fallbacks during normal typing. With 5 total bits per span, we - // can encode up to 6 spans, each as long as 15 chars with 0 or 1 char - // gap. This is sufficient for the vast majority of framework symbols. - private const int BitsForGap = 1; - private const int BitsForLength = 4; - - private const int BitsPerEncodedSpan = BitsForGap + BitsForLength; - private const int MaxShortSpans = 32 / BitsPerEncodedSpan; - private const int MaxGap = (1 << BitsForGap) - 1; - private const int MaxLength = (1 << BitsForLength) - 1; - - private struct EncodedSpans - { - private const uint Mask = (1u << BitsPerEncodedSpan) - 1u; - private uint _value; - - public byte this[int index] - { - get - { - Debug.Assert(index >= 0 && index < MaxShortSpans); - return (byte)((_value >> (index * BitsPerEncodedSpan)) & Mask); - } - set - { - Debug.Assert(index >= 0 && index < MaxShortSpans); - int shift = index * BitsPerEncodedSpan; - _value = (_value & ~(Mask << shift)) | ((uint)value << shift); - } - } - } - - public static StringBreaks Create(string text, Func spanGenerator) - { - Debug.Assert(text != null); - Debug.Assert(spanGenerator != null); - EncodedSpans encodedSpans; - return TryEncodeSpans(text, spanGenerator, out encodedSpans) - ? new StringBreaks(encodedSpans) - : new StringBreaks(CreateFallbackList(text, spanGenerator)); - } - - private static bool TryEncodeSpans(string text, Func spanGenerator, out EncodedSpans encodedSpans) - { - encodedSpans = default(EncodedSpans); - for (int start = 0, b = 0; start < text.Length;) - { - var span = spanGenerator(text, start); - if (span.IsEmpty) - { - // All done - break; - } - - int gap = span.Start - start; - Debug.Assert(gap >= 0, "Bad generator."); - - if (b >= MaxShortSpans || - span.Length > MaxLength || - gap > MaxGap) - { - // Too many spans, or span cannot be encoded. - return false; - } - - encodedSpans[b++] = Encode(gap, span.Length); - start = span.End; - } - - return true; - } - - private static List CreateFallbackList(string text, Func spanGenerator) - { - List list = new List(); - for (int start = 0; start < text.Length;) - { - var span = spanGenerator(text, start); - if (span.IsEmpty) - { - // All done - break; - } - - Debug.Assert(span.Start >= start, "Bad generator."); - - list.Add(span); - start = span.End; - } - - return list; - } - - private StringBreaks(EncodedSpans encodedSpans) - { - _encodedSpans = encodedSpans; - _spans = null; - } - - private StringBreaks(List spans) - { - _encodedSpans = default(EncodedSpans); - _spans = spans; - } - - public int Count - { - get - { - if (_spans != null) - { - return _spans.Count; - } - - int i; - for (i = 0; i < MaxShortSpans; i++) - { - if (_encodedSpans[i] == 0) break; - } - - return i; - } - } - - public TextSpan this[int index] - { - get - { - if (index < 0) - { - throw new IndexOutOfRangeException(nameof(index)); - } - - if (_spans != null) - { - return _spans[index]; - } - - for (int i = 0, start = 0; i < MaxShortSpans; i++) - { - byte b = _encodedSpans[i]; - if (b == 0) - { - break; - } - - start += DecodeGap(b); - int length = DecodeLength(b); - if (i == index) - { - return new TextSpan(start, length); - } - - start += length; - } - - throw new IndexOutOfRangeException(nameof(index)); - } - } - - private static byte Encode(int gap, int length) - { - Debug.Assert(gap >= 0 && gap <= MaxGap); - Debug.Assert(length >= 0 && length <= MaxLength); - return unchecked((byte)((gap << BitsForLength) | length)); - } - - private static int DecodeLength(byte b) => b & MaxLength; - - private static int DecodeGap(byte b) => b >> BitsForLength; - } - - internal static class StringBreaker - { - /// - /// Breaks an identifier string into constituent parts. - /// - public static StringBreaks BreakIntoCharacterParts(string identifier) => StringBreaks.Create(identifier, s_characterPartsGenerator); - - /// - /// Breaks an identifier string into constituent parts. - /// - public static StringBreaks BreakIntoWordParts(string identifier) => StringBreaks.Create(identifier, s_wordPartsGenerator); - - private static readonly Func s_characterPartsGenerator = (identifier, start) => GenerateSpan(identifier, start, word: false); - - private static readonly Func s_wordPartsGenerator = (identifier, start) => GenerateSpan(identifier, start, word: true); - - public static TextSpan GenerateSpan(string identifier, int wordStart, bool word) - { - for (int i = wordStart + 1; i < identifier.Length; i++) - { - var lastIsDigit = char.IsDigit(identifier[i - 1]); - var currentIsDigit = char.IsDigit(identifier[i]); - - var transitionFromLowerToUpper = TransitionFromLowerToUpper(identifier, word, i); - var transitionFromUpperToLower = TransitionFromUpperToLower(identifier, word, i, wordStart); - - if (char.IsPunctuation(identifier[i - 1]) || - char.IsPunctuation(identifier[i]) || - lastIsDigit != currentIsDigit || - transitionFromLowerToUpper || - transitionFromUpperToLower) - { - if (!IsAllPunctuation(identifier, wordStart, i)) - { - return new TextSpan(wordStart, i - wordStart); - } - - wordStart = i; - } - } - - if (!IsAllPunctuation(identifier, wordStart, identifier.Length)) - { - return new TextSpan(wordStart, identifier.Length - wordStart); - } - - return default(TextSpan); - } - - private static bool IsAllPunctuation(string identifier, int start, int end) - { - for (int i = start; i < end; i++) - { - var ch = identifier[i]; - - // We don't consider _ as punctuation as there may be things with that name. - if (!char.IsPunctuation(ch) || ch == '_') - { - return false; - } - } - - return true; - } - - private static bool TransitionFromUpperToLower(string identifier, bool word, int index, int wordStart) - { - if (word) - { - // Cases this supports: - // 1) IDisposable -> I, Disposable - // 2) UIElement -> UI, Element - // 3) HTMLDocument -> HTML, Document - // - // etc. - if (index != wordStart && - index + 1 < identifier.Length) - { - var currentIsUpper = char.IsUpper(identifier[index]); - var nextIsLower = char.IsLower(identifier[index + 1]); - if (currentIsUpper && nextIsLower) - { - // We have a transition from an upper to a lower letter here. But we only - // want to break if all the letters that preceded are uppercase. i.e. if we - // have "Foo" we don't want to break that into "F, oo". But if we have - // "IFoo" or "UIFoo", then we want to break that into "I, Foo" and "UI, - // Foo". i.e. the last uppercase letter belongs to the lowercase letters - // that follows. Note: this will make the following not split properly: - // "HELLOthere". However, these sorts of names do not show up in .Net - // programs. - for (int i = wordStart; i < index; i++) - { - if (!char.IsUpper(identifier[i])) - { - return false; - } - } - - return true; - } - } - } - - return false; - } - - private static bool TransitionFromLowerToUpper(string identifier, bool word, int index) - { - var lastIsUpper = char.IsUpper(identifier[index - 1]); - var currentIsUpper = char.IsUpper(identifier[index]); - - // See if the casing indicates we're starting a new word. Note: if we're breaking on - // words, then just seeing an upper case character isn't enough. Instead, it has to - // be uppercase and the previous character can't be uppercase. - // - // For example, breaking "AddMetadata" on words would make: Add Metadata - // - // on characters would be: A dd M etadata - // - // Break "AM" on words would be: AM - // - // on characters would be: A M - // - // We break the search string on characters. But we break the symbol name on words. - var transition = word - ? (currentIsUpper && !lastIsUpper) - : currentIsUpper; - return transition; - } - } -} diff --git a/vsintegration/src/FSharp.PatternMatcher/StringSlice.cs b/vsintegration/src/FSharp.PatternMatcher/StringSlice.cs deleted file mode 100644 index e3e3ebe9dd1..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/StringSlice.cs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Utilities -{ - internal struct StringSlice : IEquatable - { - private readonly string _underlyingString; - private readonly TextSpan _span; - - public StringSlice(string underlyingString, TextSpan span) - { - _underlyingString = underlyingString; - _span = span; - - Debug.Assert(span.Start >= 0); - Debug.Assert(span.End <= underlyingString.Length); - } - - public StringSlice(string value) : this(value, new TextSpan(0, value.Length)) - { - } - - public int Length => _span.Length; - - public char this[int index] => _underlyingString[_span.Start + index]; - - public Enumerator GetEnumerator() - { - return new Enumerator(this); - } - - public override bool Equals(object obj) => Equals((StringSlice)obj); - - public bool Equals(StringSlice other) => EqualsOrdinal(other); - - internal bool EqualsOrdinal(StringSlice other) - { - if (this._span.Length != other._span.Length) - { - return false; - } - - var end = this._span.End; - for (int i = this._span.Start, j = other._span.Start; i < end; i++, j++) - { - if (this._underlyingString[i] != other._underlyingString[j]) - { - return false; - } - } - - return true; - } - - internal bool EqualsOrdinalIgnoreCase(StringSlice other) - { - if (this._span.Length != other._span.Length) - { - return false; - } - - var end = this._span.End; - for (int i = this._span.Start, j = other._span.Start; i < end; i++, j++) - { - var thisChar = this._underlyingString[i]; - var otherChar = other._underlyingString[j]; - - if (!EqualsOrdinalIgnoreCase(thisChar, otherChar)) - { - return false; - } - } - - return true; - } - - private static bool EqualsOrdinalIgnoreCase(char thisChar, char otherChar) - { - // Do a fast check first before converting to lowercase characters. - return - thisChar == otherChar || - CaseInsensitiveComparison.ToLower(thisChar) == CaseInsensitiveComparison.ToLower(otherChar); - } - - public override int GetHashCode() => GetHashCodeOrdinal(); - - internal int GetHashCodeOrdinal() - { - return Hash.GetFNVHashCode(this._underlyingString, this._span.Start, this._span.Length); - } - - internal int GetHashCodeOrdinalIgnoreCase() - { - return Hash.GetCaseInsensitiveFNVHashCode(this._underlyingString, this._span.Start, this._span.Length); - } - - internal int CompareToOrdinal(StringSlice other) - { - var thisEnd = this._span.End; - var otherEnd = other._span.End; - for (int i = this._span.Start, j = other._span.Start; - i < thisEnd && j < otherEnd; - i++, j++) - { - var diff = this._underlyingString[i] - other._underlyingString[j]; - if (diff != 0) - { - return diff; - } - } - - // Choose the one that is shorter if their prefixes match so far. - return this.Length - other.Length; - } - - internal int CompareToOrdinalIgnoreCase(StringSlice other) - { - var thisEnd = this._span.End; - var otherEnd = other._span.End; - for (int i = this._span.Start, j = other._span.Start; - i < thisEnd && j < otherEnd; - i++, j++) - { - var diff = - CaseInsensitiveComparison.ToLower(this._underlyingString[i]) - - CaseInsensitiveComparison.ToLower(other._underlyingString[j]); - if (diff != 0) - { - return diff; - } - } - - // Choose the one that is shorter if their prefixes match so far. - return this.Length - other.Length; - } - - public struct Enumerator - { - private readonly StringSlice _stringSlice; - private int index; - - public Enumerator(StringSlice stringSlice) - { - _stringSlice = stringSlice; - index = -1; - } - - public bool MoveNext() - { - index++; - return index < _stringSlice.Length; - } - - public char Current => _stringSlice[index]; - } - } - - internal abstract class StringSliceComparer : IComparer, IEqualityComparer - { - public static readonly StringSliceComparer Ordinal = new OrdinalComparer(); - public static readonly StringSliceComparer OrdinalIgnoreCase = new OrdinalIgnoreCaseComparer(); - - private class OrdinalComparer : StringSliceComparer - { - public override int Compare(StringSlice x, StringSlice y) - => x.CompareToOrdinal(y); - - public override bool Equals(StringSlice x, StringSlice y) - => x.EqualsOrdinal(y); - - public override int GetHashCode(StringSlice obj) - => obj.GetHashCodeOrdinal(); - } - - private class OrdinalIgnoreCaseComparer : StringSliceComparer - { - public override int Compare(StringSlice x, StringSlice y) - => x.CompareToOrdinalIgnoreCase(y); - - public override bool Equals(StringSlice x, StringSlice y) - => x.EqualsOrdinalIgnoreCase(y); - - public override int GetHashCode(StringSlice obj) - => obj.GetHashCodeOrdinalIgnoreCase(); - } - - public abstract int Compare(StringSlice x, StringSlice y); - public abstract bool Equals(StringSlice x, StringSlice y); - public abstract int GetHashCode(StringSlice obj); - } -} \ No newline at end of file diff --git a/vsintegration/src/FSharp.PatternMatcher/TextSpan.cs b/vsintegration/src/FSharp.PatternMatcher/TextSpan.cs deleted file mode 100644 index 4a5711ce24c..00000000000 --- a/vsintegration/src/FSharp.PatternMatcher/TextSpan.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; - -namespace Roslyn.Utilities -{ - /// - /// Immutable abstract representation of a span of text. For example, in an error diagnostic that reports a - /// location, it could come from a parsed string, text from a tool editor buffer, etc. - /// - internal struct TextSpan : IEquatable, IComparable - { - /// - /// Creates a TextSpan instance beginning with the position Start and having the Length - /// specified with . - /// - public TextSpan(int start, int length) - { - if (start < 0) - { - throw new ArgumentOutOfRangeException(nameof(start)); - } - - if (start + length < start) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - Start = start; - Length = length; - } - - /// - /// Start point of the span. - /// - public int Start { get; } - - /// - /// End of the span. - /// - public int End => Start + Length; - - /// - /// Length of the span. - /// - public int Length { get; } - - /// - /// Determines whether or not the span is empty. - /// - public bool IsEmpty => this.Length == 0; - - /// - /// Determines whether the position lies within the span. - /// - /// - /// The position to check. - /// - /// - /// true if the position is greater than or equal to Start and strictly less - /// than End, otherwise false. - /// - public bool Contains(int position) - { - return unchecked((uint)(position - Start) < (uint)Length); - } - - /// - /// Determines whether falls completely within this span. - /// - /// - /// The span to check. - /// - /// - /// true if the specified span falls completely within this span, otherwise false. - /// - public bool Contains(TextSpan span) - { - return span.Start >= Start && span.End <= this.End; - } - - /// - /// Determines whether overlaps this span. Two spans are considered to overlap - /// if they have positions in common and neither is empty. Empty spans do not overlap with any - /// other span. - /// - /// - /// The span to check. - /// - /// - /// true if the spans overlap, otherwise false. - /// - public bool OverlapsWith(TextSpan span) - { - int overlapStart = Math.Max(Start, span.Start); - int overlapEnd = Math.Min(this.End, span.End); - - return overlapStart < overlapEnd; - } - - /// - /// Returns the overlap with the given span, or null if there is no overlap. - /// - /// - /// The span to check. - /// - /// - /// The overlap of the spans, or null if the overlap is empty. - /// - public TextSpan? Overlap(TextSpan span) - { - int overlapStart = Math.Max(Start, span.Start); - int overlapEnd = Math.Min(this.End, span.End); - - return overlapStart < overlapEnd - ? TextSpan.FromBounds(overlapStart, overlapEnd) - : (TextSpan?)null; - } - - /// - /// Determines whether intersects this span. Two spans are considered to - /// intersect if they have positions in common or the end of one span - /// coincides with the start of the other span. - /// - /// - /// The span to check. - /// - /// - /// true if the spans intersect, otherwise false. - /// - public bool IntersectsWith(TextSpan span) - { - return span.Start <= this.End && span.End >= Start; - } - - /// - /// Determines whether intersects this span. - /// A position is considered to intersect if it is between the start and - /// end positions (inclusive) of this span. - /// - /// - /// The position to check. - /// - /// - /// true if the position intersects, otherwise false. - /// - public bool IntersectsWith(int position) - { - return unchecked((uint)(position - Start) <= (uint)Length); - } - - /// - /// Returns the intersection with the given span, or null if there is no intersection. - /// - /// - /// The span to check. - /// - /// - /// The intersection of the spans, or null if the intersection is empty. - /// - public TextSpan? Intersection(TextSpan span) - { - int intersectStart = Math.Max(Start, span.Start); - int intersectEnd = Math.Min(this.End, span.End); - - return intersectStart <= intersectEnd - ? TextSpan.FromBounds(intersectStart, intersectEnd) - : (TextSpan?)null; - } - - /// - /// Creates a new from and positions as opposed to a position and length. - /// - /// The returned TextSpan contains the range with inclusive, - /// and exclusive. - /// - public static TextSpan FromBounds(int start, int end) - { - if (start < 0) - { - throw new ArgumentOutOfRangeException(nameof(start), "Start must not ne ngative"); - } - - if (end < start) - { - throw new ArgumentOutOfRangeException(nameof(end), "End must not be less than start"); - } - - return new TextSpan(start, end - start); - } - - /// - /// Determines if two instances of are the same. - /// - public static bool operator ==(TextSpan left, TextSpan right) - { - return left.Equals(right); - } - - /// - /// Determines if two instances of are different. - /// - public static bool operator !=(TextSpan left, TextSpan right) - { - return !left.Equals(right); - } - - /// - /// Determines if current instance of is equal to another. - /// - public bool Equals(TextSpan other) - { - return Start == other.Start && Length == other.Length; - } - - /// - /// Determines if current instance of is equal to another. - /// - public override bool Equals(object obj) - { - return obj is TextSpan && Equals((TextSpan)obj); - } - - /// - /// Produces a hash code for . - /// - public override int GetHashCode() - { - return Hash.Combine(Start, Length); - } - - /// - /// Provides a string representation for . - /// - public override string ToString() - { - return $"[{Start}..{End})"; - } - - /// - /// Compares current instance of with another. - /// - public int CompareTo(TextSpan other) - { - var diff = Start - other.Start; - if (diff != 0) - { - return diff; - } - - return Length - other.Length; - } - } -} From e47c377f65bebbf1511b6834545b5fe14ee87d2b Mon Sep 17 00:00:00 2001 From: majocha Date: Mon, 13 Feb 2023 21:54:20 +0100 Subject: [PATCH 2/4] clean up remnants --- FSharp.Editor.sln | 2 -- .../Swix/Microsoft.FSharp.IDE/Microsoft.FSharp.IDE.csproj | 1 - .../FSharp.Editor/Navigation/NavigateToSearchService.fs | 8 +++++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/FSharp.Editor.sln b/FSharp.Editor.sln index 70ffe6bb0ea..97c33e53b62 100644 --- a/FSharp.Editor.sln +++ b/FSharp.Editor.sln @@ -12,8 +12,6 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.DependencyManager.Nu EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor", "vsintegration\src\FSharp.Editor\FSharp.Editor.fsproj", "{86E148BE-92C8-47CC-A070-11D769C6D898}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.PatternMatcher", "vsintegration\src\FSharp.PatternMatcher\FSharp.PatternMatcher.csproj", "{4FFA5E03-4128-48C9-8FCD-D7C60729ED74}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.UIResources", "vsintegration\src\FSharp.UIResources\FSharp.UIResources.csproj", "{DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}" EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.VS.FSI", "vsintegration\src\FSharp.VS.FSI\FSharp.VS.FSI.fsproj", "{EAC029EB-4A8F-4966-9B38-60D73D8E20D1}" diff --git a/setup/Swix/Microsoft.FSharp.IDE/Microsoft.FSharp.IDE.csproj b/setup/Swix/Microsoft.FSharp.IDE/Microsoft.FSharp.IDE.csproj index d78d0b6d1e9..9b2aabb2741 100644 --- a/setup/Swix/Microsoft.FSharp.IDE/Microsoft.FSharp.IDE.csproj +++ b/setup/Swix/Microsoft.FSharp.IDE/Microsoft.FSharp.IDE.csproj @@ -22,7 +22,6 @@ <_Dependency Include="FSharp.Editor" Version="$(VSAssemblyVersion)" /> <_Dependency Include="FSharp.LanguageService.Base" Version="$(VSAssemblyVersion)" /> <_Dependency Include="FSharp.LanguageService" Version="$(VSAssemblyVersion)" /> - <_Dependency Include="FSharp.PatternMatcher" Version="$(VSAssemblyVersion)" /> <_Dependency Include="FSharp.ProjectSystem.Base" Version="$(VSAssemblyVersion)" /> <_Dependency Include="FSharp.ProjectSystem.FSharp" Version="$(VSAssemblyVersion)" /> <_Dependency Include="FSharp.ProjectSystem.PropertyPages" Version="$(VSAssemblyVersion)" /> diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index fd720c56035..0a479afb757 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -229,7 +229,13 @@ type internal FSharpNavigateToSearchService | PatternMatchKind.Exact -> FSharpNavigateToMatchKind.Exact | PatternMatchKind.Prefix -> FSharpNavigateToMatchKind.Prefix | PatternMatchKind.Substring -> FSharpNavigateToMatchKind.Substring - | _ -> FSharpNavigateToMatchKind.Regular + | PatternMatchKind.CamelCaseExact -> FSharpNavigateToMatchKind.CamelCaseExact + | PatternMatchKind.CamelCasePrefix -> FSharpNavigateToMatchKind.CamelCasePrefix + | PatternMatchKind.CamelCaseNonContiguousPrefix -> FSharpNavigateToMatchKind.CamelCaseNonContiguousPrefix + | PatternMatchKind.CamelCaseSubstring -> FSharpNavigateToMatchKind.CamelCaseSubstring + | PatternMatchKind.CamelCaseNonContiguousSubstring -> FSharpNavigateToMatchKind.CamelCaseNonContiguousSubstring + | PatternMatchKind.Fuzzy -> FSharpNavigateToMatchKind.Fuzzy + | _ -> FSharpNavigateToMatchKind.Fuzzy interface IFSharpNavigateToSearchService with member _.SearchProjectAsync(project, _priorityDocuments, searchPattern, kinds, cancellationToken) : Task> = From 64323c595d4664e24ff3a266d046fd0faf47a234 Mon Sep 17 00:00:00 2001 From: majocha Date: Mon, 13 Feb 2023 23:14:48 +0100 Subject: [PATCH 3/4] some formatting --- .../FSharp.Editor/Navigation/NavigateToSearchService.fs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 0a479afb757..7ccf5e780d6 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -254,13 +254,15 @@ type internal FSharpNavigateToSearchService |> Array.filter (fun x -> x.Name.Length = 1 && String.Equals(x.Name, searchPattern, StringComparison.InvariantCultureIgnoreCase)) else [| yield! items |> Array.map (fun items -> items.Find(searchPattern)) |> Array.concat - let patternMatcherOptions = PatternMatcherCreationOptions(cultureInfo = CultureInfo.CurrentUICulture, flags = PatternMatcherCreationFlags.AllowFuzzyMatching) + let patternMatcherOptions = PatternMatcherCreationOptions( + cultureInfo = CultureInfo.CurrentUICulture, + flags = PatternMatcherCreationFlags.AllowFuzzyMatching + ) let patternMatcher = patternMatcherFactory.CreatePatternMatcher(searchPattern, patternMatcherOptions) for item in items |> Array.collect (fun item -> item.AllItems) do match patternMatcher.TryMatch(item.LogicalName) |> Option.ofNullable with | Some pm -> yield NavigateToSearchResult(item, patternMatchKindToNavigateToMatchKind pm.Kind) :> FSharpNavigateToSearchResult - | _ -> () - |] + | _ -> () |] return items |> Array.distinctBy (fun x -> x.NavigableItem.Document.Id, x.NavigableItem.SourceSpan) } From 6891473ae1870b9392146f9d6d2d80cbf611d625 Mon Sep 17 00:00:00 2001 From: majocha Date: Mon, 13 Feb 2023 23:45:53 +0100 Subject: [PATCH 4/4] restore Array.Parallel --- .../Navigation/NavigateToSearchService.fs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 7ccf5e780d6..c451a105e05 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -259,10 +259,13 @@ type internal FSharpNavigateToSearchService flags = PatternMatcherCreationFlags.AllowFuzzyMatching ) let patternMatcher = patternMatcherFactory.CreatePatternMatcher(searchPattern, patternMatcherOptions) - for item in items |> Array.collect (fun item -> item.AllItems) do - match patternMatcher.TryMatch(item.LogicalName) |> Option.ofNullable with - | Some pm -> yield NavigateToSearchResult(item, patternMatchKindToNavigateToMatchKind pm.Kind) :> FSharpNavigateToSearchResult - | _ -> () |] + yield! items + |> Array.collect (fun item -> item.AllItems) + |> Array.Parallel.choose (fun x -> + patternMatcher.TryMatch(x.LogicalName) + |> Option.ofNullable + |> Option.map (fun pm -> + NavigateToSearchResult(x, patternMatchKindToNavigateToMatchKind pm.Kind) :> FSharpNavigateToSearchResult)) |] return items |> Array.distinctBy (fun x -> x.NavigableItem.Document.Id, x.NavigableItem.SourceSpan) }