|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNetCore.Razor.Language.Syntax;
internal readonly partial struct ChildSyntaxList : IEquatable<ChildSyntaxList>, IReadOnlyList<SyntaxNodeOrToken>
{
private readonly SyntaxNode _node;
private readonly int _count;
internal ChildSyntaxList(SyntaxNode node)
{
_node = node;
_count = CountNodes(node.Green);
}
/// <summary>
/// Gets the number of children contained in the <see cref="ChildSyntaxList"/>.
/// </summary>
public int Count => _count;
internal static int CountNodes(GreenNode green)
{
var n = 0;
for (int i = 0, s = green.SlotCount; i < s; i++)
{
var child = green.GetSlot(i);
if (child != null)
{
if (!child.IsList)
{
n++;
}
else
{
n += child.SlotCount;
}
}
}
return n;
}
/// <summary>
/// Gets the child at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the child to get.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="index"/> is less than 0.-or-<paramref name="index" /> is equal to or greater than <see cref="ChildSyntaxList.Count"/>.
/// </exception>
public SyntaxNodeOrToken this[int index]
{
get
{
if (unchecked((uint)index < (uint)_count))
{
return ItemInternal(_node, index);
}
throw new ArgumentOutOfRangeException(nameof(index));
}
}
internal SyntaxNode Node => _node;
private static int Occupancy(GreenNode green)
{
return green.IsList ? green.SlotCount : 1;
}
internal readonly struct SlotData
{
/// <summary>
/// The green node slot index at which to start the search
/// </summary>
public readonly int SlotIndex;
/// <summary>
/// Indicates the total number of occupants in preceding slots
/// </summary>
public readonly int PrecedingOccupantSlotCount;
/// <summary>
/// Indicates the node start position plus any prior slot full widths
/// </summary>
public readonly int PositionAtSlotIndex;
public SlotData(SyntaxNode node)
: this(slotIndex: 0, precedingOccupantSlotCount: 0, node.Position)
{
}
public SlotData(int slotIndex, int precedingOccupantSlotCount, int positionAtSlotIndex)
{
SlotIndex = slotIndex;
PrecedingOccupantSlotCount = precedingOccupantSlotCount;
PositionAtSlotIndex = positionAtSlotIndex;
}
}
internal static SyntaxNodeOrToken ItemInternal(SyntaxNode node, int index)
{
var slotData = new SlotData(node);
return ItemInternal(node, index, ref slotData);
}
/// <summary>
/// An internal indexer that does not verify index.
/// Used when caller has already ensured that index is within bounds.
/// </summary>
internal static SyntaxNodeOrToken ItemInternal(SyntaxNode node, int index, ref SlotData slotData)
{
GreenNode? greenChild;
var green = node.Green;
// slotData may contain information that allows us to start the loop below using data
// calculated during a previous call. As index represents the offset into all children of
// node, idx represents the offset requested relative to the given slot index.
var idx = index - slotData.PrecedingOccupantSlotCount;
var slotIndex = slotData.SlotIndex;
var position = slotData.PositionAtSlotIndex;
Debug.Assert(idx >= 0);
// find a slot that contains the node or its parent list (if node is in a list)
// we will be skipping whole slots here so we will not loop for long
//
// at the end of this loop we will have
// 1) slot index - slotIdx
// 2) if the slot is a list, node index in the list - idx
// 3) slot position - position
while (true)
{
greenChild = green.GetSlot(slotIndex);
if (greenChild != null)
{
var currentOccupancy = Occupancy(greenChild);
if (idx < currentOccupancy)
{
break;
}
idx -= currentOccupancy;
position += greenChild.Width;
}
slotIndex++;
}
if (slotIndex != slotData.SlotIndex)
{
// (index - idx) represents the number of occupants prior to this new slotIndex
slotData = new SlotData(slotIndex, index - idx, position);
}
// get node that represents this slot
var red = node.GetNodeSlot(slotIndex);
if (!greenChild.IsList)
{
// this is a single node
// if it is a node, we are done
if (red != null)
{
return red;
}
}
else if (red != null)
{
// it is a red list of nodes (separated or not), most common case
var redChild = red.GetNodeSlot(idx);
if (redChild != null)
{
// this is our node
return redChild;
}
// must be a separator
// update greenChild and position and let it be handled as a token
greenChild = greenChild.GetSlot(idx);
position = red.GetChildPosition(idx);
}
else
{
// it is a token from a token list, uncommon case
// update greenChild and position and let it be handled as a token
position += greenChild.GetSlotOffset(idx);
greenChild = greenChild.GetSlot(idx);
}
return new SyntaxNodeOrToken(node, greenChild, position, index);
}
/// <summary>
/// Locate the node that is a child of the given <see cref="SyntaxNode"/> and contains the given position.
/// </summary>
/// <param name="node">The <see cref="SyntaxNode"/> to search.</param>
/// <param name="targetPosition">The position.</param>
/// <returns>
/// The node that spans the given position.
/// </returns>
/// <remarks>
/// Assumes that <paramref name="targetPosition"/> is within the span of <paramref name="node"/>.
/// </remarks>
internal static SyntaxNodeOrToken ChildThatContainsPosition(SyntaxNode node, int targetPosition)
{
// The targetPosition must already be within this node
Debug.Assert(node.Span.Contains(targetPosition));
var green = node.Green;
var position = node.Position;
var index = 0;
Debug.Assert(!green.IsList);
// Find the green node that spans the target position.
// We will be skipping whole slots here so we will not loop for long
int slot;
for (slot = 0; ; slot++)
{
var greenChild = green.GetSlot(slot);
if (greenChild != null)
{
var endPosition = position + greenChild.Width;
if (targetPosition < endPosition)
{
// Descend into the child element
green = greenChild;
break;
}
position = endPosition;
index += Occupancy(greenChild);
}
}
// Realize the red node (if any)
var red = node.GetNodeSlot(slot);
if (!green.IsList)
{
// This is a single node.
// If it is a node, we are done.
if (red != null)
{
return red;
}
// Otherwise will have to make a token with current green and position
}
else
{
slot = green.FindSlotIndexContainingOffset(targetPosition - position);
// Realize the red node (if any)
if (red != null)
{
// It is a red list of nodes
red = red.GetNodeSlot(slot);
if (red != null)
{
return red;
}
}
// Otherwise we have a token.
position += green.GetSlotOffset(slot);
green = green.GetSlot(slot);
// Since we can't have "lists of lists", the Occupancy calculation for
// child elements in a list is simple.
index += slot;
}
// Make a token with current child and position.
return new SyntaxNodeOrToken(node, green, position, index);
}
/// <summary>
/// An internal indexer that does not verify index.
/// Used when caller has already ensured that index is within bounds.
/// </summary>
internal static SyntaxNode? ItemInternalAsNode(SyntaxNode node, int index, ref SlotData slotData)
{
GreenNode? greenChild;
var green = node.Green;
var idx = index - slotData.PrecedingOccupantSlotCount;
var slotIndex = slotData.SlotIndex;
var position = slotData.PositionAtSlotIndex;
Debug.Assert(idx >= 0);
// find a slot that contains the node or its parent list (if node is in a list)
// we will be skipping whole slots here so we will not loop for long
//
// at the end of this loop we will have
// 1) slot index - slotIdx
// 2) if the slot is a list, node index in the list - idx
while (true)
{
greenChild = green.GetSlot(slotIndex);
if (greenChild != null)
{
var currentOccupancy = Occupancy(greenChild);
if (idx < currentOccupancy)
{
break;
}
idx -= currentOccupancy;
position += greenChild.Width;
}
slotIndex++;
}
if (slotIndex != slotData.SlotIndex)
{
// (index - idx) represents the number of occupants prior to this new slotIndex
slotData = new SlotData(slotIndex, index - idx, position);
}
// get node that represents this slot
var red = node.GetNodeSlot(slotIndex);
if (greenChild.IsList && red != null)
{
// it is a red list of nodes, most common case
return red.GetNodeSlot(idx);
}
// this is a single node
return red;
}
// for debugging
#pragma warning disable IDE0051 // Remove unused private members
private SyntaxNodeOrToken[] NodesAndTokens => [.. this];
#pragma warning restore IDE0051 // Remove unused private members
public bool Any()
=> _count != 0;
/// <summary>
/// Returns the first child in the list.
/// </summary>
/// <returns>
/// The first child in the list.
/// </returns>
/// <exception cref="InvalidOperationException">The list is empty.</exception>
public SyntaxNodeOrToken First()
{
if (Any())
{
return this[0];
}
throw new InvalidOperationException();
}
/// <summary>
/// Returns the last child in the list.
/// </summary>
/// <returns>
/// The last child in the list.
/// </returns>
/// <exception cref="InvalidOperationException">The list is empty.</exception>
public SyntaxNodeOrToken Last()
{
if (Any())
{
return this[_count - 1];
}
throw new InvalidOperationException();
}
/// <summary>
/// Returns a list which contains all children of <see cref="ChildSyntaxList"/> in reversed order.
/// </summary>
/// <returns>
/// <see cref="Reversed"/> which contains all children of <see cref="ChildSyntaxList"/> in reversed order.
/// </returns>
public Reversed Reverse()
{
Debug.Assert(_node != null);
return new Reversed(_node, _count);
}
/// <summary>
/// Returns an enumerator that iterates through the <see cref="ChildSyntaxList"/>.
/// </summary>
/// <returns>
/// A <see cref="Enumerator"/> for the <see cref="ChildSyntaxList"/>.
/// </returns>
public Enumerator GetEnumerator()
=> _node != null ? new Enumerator(_node, _count) : default;
IEnumerator<SyntaxNodeOrToken> IEnumerable<SyntaxNodeOrToken>.GetEnumerator()
=> _node == null
? SpecializedCollections.EmptyEnumerator<SyntaxNodeOrToken>()
: new EnumeratorImpl(_node, _count);
IEnumerator IEnumerable.GetEnumerator()
=> _node == null
? SpecializedCollections.EmptyEnumerator<SyntaxNodeOrToken>()
: new EnumeratorImpl(_node, _count);
/// <summary>
/// Determines whether the specified object is equal to the current instance.
/// </summary>
/// <param name="obj">The object to be compared with the current instance.</param>
/// <returns>
/// <see langword="true"/> if the specified object is a <see cref="ChildSyntaxList" /> structure and is equal to the current instance;
/// otherwise, <see langword="false"/>.
/// </returns>
public override bool Equals([NotNullWhen(true)] object? obj)
=> obj is ChildSyntaxList list && Equals(list);
/// <summary>
/// Determines whether the specified <see cref="ChildSyntaxList" /> structure is equal to the current instance.
/// </summary>
/// <param name="other">The <see cref="ChildSyntaxList" /> structure to be compared with the current instance.</param>
/// <returns>
/// <see langword="true"/> if the specified <see cref="ChildSyntaxList" /> structure is equal to the current instance;
/// otherwise, <see langword="false"/>.
/// </returns>
public bool Equals(ChildSyntaxList other)
=> _node == other._node;
/// <summary>
/// Returns the hash code for the current instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer hash code.
/// </returns>
public override int GetHashCode()
=> _node?.GetHashCode() ?? 0;
/// <summary>
/// Indicates whether two <see cref="ChildSyntaxList" /> structures are equal.
/// </summary>
/// <param name="list1">The <see cref="ChildSyntaxList" /> structure on the left side of the equality operator.</param>
/// <param name="list2">The <see cref="ChildSyntaxList" /> structure on the right side of the equality operator.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="list1" /> is equal to <paramref name="list2" />;
/// otherwise, <see langword="false"/>.
/// </returns>
public static bool operator ==(ChildSyntaxList list1, ChildSyntaxList list2)
=> list1.Equals(list2);
/// <summary>
/// Indicates whether two <see cref="ChildSyntaxList" /> structures are unequal.
/// </summary>
/// <param name="list1">The <see cref="ChildSyntaxList" /> structure on the left side of the inequality operator.</param>
/// <param name="list2">The <see cref="ChildSyntaxList" /> structure on the right side of the inequality operator.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="list1" /> is not equal to <paramref name="list2" />;
/// otherwise, <see langword="false"/>.
/// </returns>
public static bool operator !=(ChildSyntaxList list1, ChildSyntaxList list2)
=> !list1.Equals(list2);
}
|