File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\EmbeddedLanguages\VirtualChars\VirtualCharSequence.cs
Web Access
Project: src\src\RoslynAnalyzers\Microsoft.CodeAnalysis.Analyzers\Core\Microsoft.CodeAnalysis.Analyzers.csproj (Microsoft.CodeAnalysis.Analyzers)
// 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;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
 
/// <summary>
/// Represents the individual characters that raw string token represents (i.e. with escapes collapsed).  
/// The difference between this and the result from token.ValueText is that for each collapsed character
/// returned the original span of text in the original token can be found.  i.e. if you had the
/// following in C#:
/// <para/>
/// <c>"G\u006fo"</c>
/// <para/>
/// Then you'd get back:
/// <para/>
/// <c>'G' -> [0, 1) 'o' -> [1, 7) 'o' -> [7, 1)</c>
/// <para/>
/// This allows for embedded language processing that can refer back to the user's original code
/// instead of the escaped value we're processing.
/// </summary>
internal partial struct VirtualCharGreenSequence
{
    public static readonly VirtualCharGreenSequence Empty = Create(ImmutableSegmentedList<VirtualCharGreen>.Empty);
 
    public static VirtualCharGreenSequence Create(ImmutableSegmentedList<VirtualCharGreen> virtualChars)
        => new(new ImmutableSegmentedListChunk(virtualChars));
 
    public static VirtualCharGreenSequence Create(string underlyingData)
        => new(new StringChunk(underlyingData));
 
    /// <summary>
    /// The actual characters that this <see cref="VirtualCharSequence"/> is a portion of.
    /// </summary>
    private readonly Chunk _leafCharacters;
 
    /// <summary>
    /// The portion of <see cref="_leafCharacters"/> that is being exposed.  This span 
    /// is `[inclusive, exclusive)`.
    /// </summary>
    private readonly TextSpan _span;
 
    private VirtualCharGreenSequence(Chunk sequence)
        : this(sequence, new TextSpan(0, sequence.Length))
    {
    }
 
    private VirtualCharGreenSequence(Chunk sequence, TextSpan span)
    {
        if (span.Start > sequence.Length)
            throw new ArgumentException();
 
        if (span.End > sequence.Length)
            throw new ArgumentException();
 
        _leafCharacters = sequence;
        _span = span;
    }
 
    /// <summary>
    /// Gets the number of elements contained in the <see cref="VirtualCharSequence"/>.
    /// </summary>
    public int Length => _span.Length;
 
    /// <summary>
    /// Gets the <see cref="VirtualChar"/> at the specified index.
    /// </summary>
    public VirtualCharGreen this[int index] => _leafCharacters[_span.Start + index];
 
    /// <summary>
    /// Gets a value indicating whether the <see cref="VirtualCharSequence"/> was declared but not initialized.
    /// </summary>
    public bool IsDefault => _leafCharacters == null;
 
    /// <summary>
    /// Retreives a sub-sequence from this <see cref="VirtualCharSequence"/>.
    /// </summary>
    public VirtualCharGreenSequence Slice(int start, int length)
        => new(_leafCharacters, new TextSpan(_span.Start + start, length));
 
    /// <summary>
    /// Finds the index of the virtual char in this sequence that contains (not just intersects) the position.  Will
    /// return null if there is no such virtual char in this sequence.
    /// </summary>
    public int? FindIndex(int tokenStart, int position)
        => _leafCharacters?.FindIndex(tokenStart, position) - _span.Start;
 
    [Conditional("DEBUG")]
    public void AssertAdjacentTo(VirtualCharGreenSequence virtualChars)
    {
        Debug.Assert(_leafCharacters == virtualChars._leafCharacters);
        Debug.Assert(_span.End == virtualChars._span.Start);
    }
 
    /// <summary>
    /// Combines two <see cref="VirtualCharGreenSequence"/>s, producing a final sequence that points at the same
    /// underlying data, but spans from the start of <paramref name="chars1"/> to the end of <paramref name="chars2"/>.
    /// </summary>  
    public static VirtualCharGreenSequence FromBounds(
        VirtualCharGreenSequence chars1, VirtualCharGreenSequence chars2)
    {
        Debug.Assert(chars1._leafCharacters == chars2._leafCharacters);
        return new VirtualCharGreenSequence(
            chars1._leafCharacters,
            TextSpan.FromBounds(chars1._span.Start, chars2._span.End));
    }
}
 
/// <inheritdoc cref="VirtualCharGreenSequence"/>
internal readonly struct VirtualCharSequence
{
    private readonly int _tokenStart;
    private readonly VirtualCharGreenSequence _sequence;
 
    public static readonly VirtualCharSequence Empty = new(0, VirtualCharGreenSequence.Empty);
 
    public static VirtualCharSequence Create(int tokenStart, string text)
        => new(tokenStart, VirtualCharGreenSequence.Create(text));
 
    public VirtualCharSequence(int tokenStart, VirtualCharGreenSequence sequence)
    {
        if (tokenStart < 0)
            throw new ArgumentException("tokenStart cannot be negative", nameof(tokenStart));
 
        _tokenStart = tokenStart;
        _sequence = sequence;
    }
 
    /// <inheritdoc cref="VirtualCharGreenSequence.Length"/>
    public int Length => _sequence.Length;
 
    public VirtualChar this[int index]
        => new(_sequence[index], _tokenStart);
 
    /// <summary>
    /// Returns the index of the <see cref="VirtualChar"/> in this <see cref="VirtualCharSequence"/> that contains the
    /// given position. Will return null if this position is not in the span of this sequence.
    /// </summary>
    public int? FindIndex(int position)
        => _sequence.FindIndex(_tokenStart, position);
 
    /// <inheritdoc cref="VirtualCharGreenSequence.IsDefault"/>
    public bool IsDefault => _sequence.IsDefault;
 
    /// <inheritdoc cref="VirtualCharGreenSequence.Slice"/>
    public VirtualCharSequence Slice(int start, int length)
       => new(_tokenStart, _sequence.Slice(start, length));
 
    public Enumerator GetEnumerator()
        => new(this);
 
    [Conditional("DEBUG")]
    public void AssertAdjacentTo(VirtualCharSequence virtualChars)
    {
        _sequence.AssertAdjacentTo(virtualChars._sequence);
    }
 
    /// <summary>
    /// Combines two <see cref="VirtualCharSequence"/>s, producing a final sequence that points at the same underlying
    /// data, but spans from the start of <paramref name="chars1"/> to the end of <paramref name="chars2"/>.
    /// </summary>  
    [Obsolete("Only around for ASP.NET compatibility. Do not use anymore.", error: false)]
    public static VirtualCharSequence FromBounds(
        VirtualCharSequence chars1, VirtualCharSequence chars2)
    {
        Debug.Assert(chars1._tokenStart == chars2._tokenStart);
        return new VirtualCharSequence(
            chars1._tokenStart,
            VirtualCharGreenSequence.FromBounds(chars1._sequence, chars2._sequence));
    }
 
    public struct Enumerator(VirtualCharSequence virtualCharSequence) : IEnumerator<VirtualChar>
    {
        private int _position = -1;
 
        public bool MoveNext() => ++_position < virtualCharSequence.Length;
        public readonly VirtualChar Current => virtualCharSequence[_position];
 
        public void Reset()
            => _position = -1;
 
        readonly object? IEnumerator.Current => this.Current;
        public readonly void Dispose() { }
    }
}
 
internal static class VirtualCharSequenceExtensions
{
    public static VirtualChar? Find(this VirtualCharSequence sequence, int position)
    {
        var index = sequence.FindIndex(position);
        return index is null ? null : sequence[index.Value];
    }
 
    public static bool IsEmpty(this VirtualCharSequence sequence) => sequence.Length == 0;
 
    public static bool IsDefaultOrEmpty(this VirtualCharSequence sequence) => sequence.IsDefault || sequence.IsEmpty();
 
    public static bool Contains(this VirtualCharSequence sequence, VirtualChar @char)
        => sequence.IndexOf(@char) >= 0;
 
    public static int IndexOf(this VirtualCharSequence sequence, VirtualChar @char)
    {
        var index = 0;
        foreach (var ch in sequence)
        {
            if (ch == @char)
                return index;
 
            index++;
        }
 
        return -1;
    }
 
    /// <summary>
    /// Create a <see cref="string"/> from the <see cref="VirtualCharSequence"/>.
    /// </summary>
    public static string CreateString(this VirtualCharSequence sequence)
    {
        using var _ = PooledStringBuilder.GetInstance(out var builder);
        foreach (var ch in sequence)
            builder.Append(ch);
 
        return builder.ToString();
    }
 
    public static string CreateString(this ImmutableSegmentedList<VirtualChar> sequence)
    {
        using var _ = PooledStringBuilder.GetInstance(out var builder);
        foreach (var ch in sequence)
            builder.Append(ch);
 
        return builder.ToString();
    }
 
    public static VirtualChar? FirstOrNull(this VirtualCharSequence sequence, Func<VirtualChar, bool> predicate)
    {
        foreach (var ch in sequence)
        {
            if (predicate(ch))
                return ch;
        }
 
        return null;
    }
 
    public static VirtualChar? LastOrNull(this VirtualCharSequence sequence, Func<VirtualChar, bool> predicate)
    {
        for (var i = sequence.Length - 1; i >= 0; i--)
        {
            var ch = sequence[i];
            if (predicate(ch))
                return ch;
        }
 
        return null;
    }
 
    public static bool Any(this VirtualCharSequence sequence, Func<VirtualChar, bool> predicate)
    {
        foreach (var ch in sequence)
        {
            if (predicate(ch))
                return true;
        }
 
        return false;
    }
 
    public static bool All(this VirtualCharSequence sequence, Func<VirtualChar, bool> predicate)
    {
        foreach (var ch in sequence)
        {
            if (!predicate(ch))
                return false;
        }
 
        return true;
    }
 
    public static VirtualCharSequence SkipWhile(this VirtualCharSequence sequence, Func<VirtualChar, bool> predicate)
    {
        var start = 0;
        foreach (var ch in sequence)
        {
            if (!predicate(ch))
                break;
 
            start++;
        }
 
        return sequence[start..];
    }
}