// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.Collections; namespace Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; internal readonly partial struct VirtualCharGreenSequence { /// <summary> /// Abstraction over a contiguous chunk of <see cref="VirtualChar"/>s. This /// is used so we can expose <see cref="VirtualChar"/>s over an <see cref="ImmutableArray{VirtualChar}"/> /// or over a <see cref="string"/>. The latter is especially useful for reducing /// memory usage in common cases of string tokens without escapes. /// </summary> private abstract partial class Chunk { public abstract int Length { get; } public abstract VirtualCharGreen this[int index] { get; } public abstract int? FindIndex(int tokenStart, int position); } /// <summary> /// Thin wrapper over an actual <see cref="ImmutableSegmentedList{T}"/>. /// This will be the common construct we generate when getting the /// <see cref="Chunk"/> for a string token that has escapes in it. /// </summary> private sealed class ImmutableSegmentedListChunk(ImmutableSegmentedList<VirtualCharGreen> array) : Chunk { public override int Length => array.Count; public override VirtualCharGreen this[int index] => array[index]; public override int? FindIndex(int tokenStart, int position) { if (array.IsEmpty) return null; if (position < new VirtualChar(array[0], tokenStart).Span.Start || position >= new VirtualChar(array[^1], tokenStart).Span.End) { return null; } var index = array.BinarySearch((tokenStart, position), static (ch, tuple) => { var (tokenStart, position) = tuple; var span = new VirtualChar(ch, tokenStart).Span; if (position < span.Start) return 1; if (position >= span.End) return -1; return 0; }); // Characters can be discontinuous (for example, in multi-line-raw-string literals). So if the position is // in one of the gaps, it won't be able to find a corresponding virtual char. return index < 0 ? null : index; } } /// <summary> /// Represents a <see cref="Chunk"/> on top of a normal string. This is the common case of the type of the sequence /// we would create for a normal string token without any escapes in it. /// </summary> /// <param name="data"> /// The underlying string that we're returning virtual chars from. Note: this will commonly include things like /// quote characters. Clients who do not want that should then ask for an appropriate <see /// cref="VirtualCharSequence.Slice"/> back that does not include those characters. /// </param> private sealed class StringChunk(string data) : Chunk { public override int Length => data.Length; public override int? FindIndex(int tokenStart, int position) { var stringIndex = position - tokenStart; return stringIndex >= 0 && stringIndex < data.Length ? stringIndex : null; } public override VirtualCharGreen this[int index] => new(data[index], offset: index, width: 1); } } |