|
// 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..];
}
}
|