|
// 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.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
public static class SeparatedSyntaxList
{
public static SeparatedSyntaxList<TNode> Create<TNode>(ReadOnlySpan<TNode> nodes) where TNode : SyntaxNode
{
if (nodes.Length == 0)
return default;
if (nodes.Length == 1)
return new SeparatedSyntaxList<TNode>(new SyntaxNodeOrTokenList(nodes[0], index: 0));
var builder = new CodeAnalysis.Syntax.SeparatedSyntaxListBuilder<TNode>(nodes.Length);
builder.Add(nodes[0]);
var separator = nodes[0].Green.CreateSeparator(nodes[0]);
for (int i = 1, n = nodes.Length; i < n; i++)
{
builder.AddSeparator(separator);
builder.Add(nodes[i]);
}
return builder.ToList();
}
}
[CollectionBuilder(typeof(SeparatedSyntaxList), "Create")]
public readonly partial struct SeparatedSyntaxList<TNode> : IEquatable<SeparatedSyntaxList<TNode>>, IReadOnlyList<TNode> where TNode : SyntaxNode
{
private readonly SyntaxNodeOrTokenList _list;
private readonly int _count;
private readonly int _separatorCount;
internal SeparatedSyntaxList(SyntaxNodeOrTokenList list)
: this()
{
Validate(list);
// calculating counts is very cheap when list interleaves nodes and tokens
// so lets just do it here.
int allCount = list.Count;
_count = (allCount + 1) >> 1;
_separatorCount = allCount >> 1;
_list = list;
}
[Conditional("DEBUG")]
private static void Validate(SyntaxNodeOrTokenList list)
{
for (int i = 0; i < list.Count; i++)
{
var item = list[i];
if ((i & 1) == 0)
{
Debug.Assert(item.IsNode, "Node missing in separated list.");
}
else
{
Debug.Assert(item.IsToken, "Separator token missing in separated list.");
}
}
}
internal SeparatedSyntaxList(SyntaxNode node, int index)
: this(new SyntaxNodeOrTokenList(node, index))
{
}
internal SyntaxNode? Node
{
get
{
return _list.Node;
}
}
public int Count
{
get
{
return _count;
}
}
public int SeparatorCount
{
get
{
return _separatorCount;
}
}
public TNode this[int index]
{
get
{
var node = _list.Node;
if (node != null)
{
if (!node.IsList)
{
if (index == 0)
{
return (TNode)node;
}
}
else
{
if (unchecked((uint)index < (uint)_count))
{
return (TNode)node.GetRequiredNodeSlot(index << 1);
}
}
}
throw new ArgumentOutOfRangeException(nameof(index));
}
}
/// <summary>
/// Gets the separator at the given index in this list.
/// </summary>
/// <param name="index">The index.</param>
/// <returns></returns>
public SyntaxToken GetSeparator(int index)
{
var node = _list.Node;
if (node != null)
{
Debug.Assert(node.IsList, "separated list cannot be a singleton separator");
if (unchecked((uint)index < (uint)_separatorCount))
{
index = (index << 1) + 1;
var green = node.Green.GetRequiredSlot(index);
Debug.Assert(green.IsToken);
return new SyntaxToken(node.Parent, green, node.GetChildPosition(index), _list.index + index);
}
}
throw new ArgumentOutOfRangeException(nameof(index));
}
/// <summary>
/// Returns the sequence of just the separator tokens.
/// </summary>
public IEnumerable<SyntaxToken> GetSeparators()
{
return _list.Where(n => n.IsToken).Select(n => n.AsToken());
}
/// <summary>
/// The absolute span of the list elements in characters, including the leading and trailing trivia of the first and last elements.
/// </summary>
public TextSpan FullSpan
{
get { return _list.FullSpan; }
}
/// <summary>
/// The absolute span of the list elements in characters, not including the leading and trailing trivia of the first and last elements.
/// </summary>
public TextSpan Span
{
get { return _list.Span; }
}
/// <summary>
/// Returns the string representation of the nodes in this list including separators but not including
/// the first node's leading trivia and the last node or token's trailing trivia.
/// </summary>
/// <returns>
/// The string representation of the nodes in this list including separators but not including
/// the first node's leading trivia and the last node or token's trailing trivia.
/// </returns>
public override string ToString()
{
return _list.ToString();
}
/// <summary>
/// Returns the full string representation of the nodes in this list including separators,
/// the first node's leading trivia, and the last node or token's trailing trivia.
/// </summary>
/// <returns>
/// The full string representation of the nodes in this list including separators including separators,
/// the first node's leading trivia, and the last node or token's trailing trivia.
/// </returns>
public string ToFullString()
{
return _list.ToFullString();
}
public TNode First()
{
return this[0];
}
public TNode? FirstOrDefault()
{
if (this.Any())
{
return this[0];
}
return null;
}
public TNode Last()
{
return this[this.Count - 1];
}
public TNode? LastOrDefault()
{
if (this.Any())
{
return this[this.Count - 1];
}
return null;
}
public bool Contains(TNode node)
{
return this.IndexOf(node) >= 0;
}
public int IndexOf(TNode node)
{
for (int i = 0, n = this.Count; i < n; i++)
{
if (object.Equals(this[i], node))
{
return i;
}
}
return -1;
}
public int IndexOf(Func<TNode, bool> predicate)
{
for (int i = 0, n = this.Count; i < n; i++)
{
if (predicate(this[i]))
{
return i;
}
}
return -1;
}
internal int IndexOf(int rawKind)
{
for (int i = 0, n = this.Count; i < n; i++)
{
if (this[i].RawKind == rawKind)
{
return i;
}
}
return -1;
}
public int LastIndexOf(TNode node)
{
for (int i = this.Count - 1; i >= 0; i--)
{
if (object.Equals(this[i], node))
{
return i;
}
}
return -1;
}
public int LastIndexOf(Func<TNode, bool> predicate)
{
for (int i = this.Count - 1; i >= 0; i--)
{
if (predicate(this[i]))
{
return i;
}
}
return -1;
}
public bool Any()
{
return _list.Any();
}
internal bool Any(Func<TNode, bool> predicate)
{
for (int i = 0; i < this.Count; i++)
{
if (predicate(this[i]))
{
return true;
}
}
return false;
}
public SyntaxNodeOrTokenList GetWithSeparators()
{
return _list;
}
public static bool operator ==(SeparatedSyntaxList<TNode> left, SeparatedSyntaxList<TNode> right)
{
return left.Equals(right);
}
public static bool operator !=(SeparatedSyntaxList<TNode> left, SeparatedSyntaxList<TNode> right)
{
return !left.Equals(right);
}
public bool Equals(SeparatedSyntaxList<TNode> other)
{
return _list == other._list;
}
public override bool Equals(object? obj)
{
return (obj is SeparatedSyntaxList<TNode> list) && Equals(list);
}
public override int GetHashCode()
{
return _list.GetHashCode();
}
/// <summary>
/// Creates a new list with the specified node added to the end.
/// </summary>
/// <param name="node">The node to add.</param>
public SeparatedSyntaxList<TNode> Add(TNode node)
{
return Insert(this.Count, node);
}
/// <summary>
/// Creates a new list with the specified nodes added to the end.
/// </summary>
/// <param name="nodes">The nodes to add.</param>
public SeparatedSyntaxList<TNode> AddRange(IEnumerable<TNode> nodes)
{
return InsertRange(this.Count, nodes);
}
/// <summary>
/// Creates a new list with the specified node inserted at the index.
/// </summary>
/// <param name="index">The index to insert at.</param>
/// <param name="node">The node to insert.</param>
public SeparatedSyntaxList<TNode> Insert(int index, TNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
return InsertRange(index, new[] { node });
}
/// <summary>
/// Creates a new list with the specified nodes inserted at the index.
/// </summary>
/// <param name="index">The index to insert at.</param>
/// <param name="nodes">The nodes to insert.</param>
public SeparatedSyntaxList<TNode> InsertRange(int index, IEnumerable<TNode> nodes)
{
if (nodes == null)
{
throw new ArgumentNullException(nameof(nodes));
}
if (index < 0 || index > this.Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
var nodesWithSeps = this.GetWithSeparators();
int insertionIndex = index < this.Count ? nodesWithSeps.IndexOf(this[index]) : nodesWithSeps.Count;
// determine how to deal with separators (commas)
if (insertionIndex > 0 && insertionIndex < nodesWithSeps.Count)
{
var previous = nodesWithSeps[insertionIndex - 1];
if (previous.IsToken && !KeepSeparatorWithPreviousNode(previous.AsToken()))
{
// pull back so item in inserted before separator
insertionIndex--;
}
}
var nodesToInsertWithSeparators = new List<SyntaxNodeOrToken>();
foreach (var item in nodes)
{
if (item != null)
{
// if item before insertion point is a node, add a separator
if (nodesToInsertWithSeparators.Count > 0 || (insertionIndex > 0 && nodesWithSeps[insertionIndex - 1].IsNode))
{
nodesToInsertWithSeparators.Add(item.Green.CreateSeparator(item));
}
nodesToInsertWithSeparators.Add(item);
}
}
// if item after last inserted node is a node, add separator
if (insertionIndex < nodesWithSeps.Count && nodesWithSeps[insertionIndex] is { IsNode: true } nodeOrToken)
{
var node = nodesWithSeps[insertionIndex].AsNode();
Debug.Assert(node is object);
nodesToInsertWithSeparators.Add(node.Green.CreateSeparator(node)); // separator
}
return new SeparatedSyntaxList<TNode>(nodesWithSeps.InsertRange(insertionIndex, nodesToInsertWithSeparators));
}
private static bool KeepSeparatorWithPreviousNode(in SyntaxToken separator)
{
// if the trivia after the separator contains an explicit end of line or a single line comment
// then it should stay associated with previous node
foreach (var tr in separator.TrailingTrivia)
{
Debug.Assert(tr.UnderlyingNode is object);
if (tr.UnderlyingNode.IsTriviaWithEndOfLine())
{
return true;
}
}
return false;
}
/// <summary>
/// Creates a new list with the element at the specified index removed.
/// </summary>
/// <param name="index">The index of the element to remove.</param>
public SeparatedSyntaxList<TNode> RemoveAt(int index)
{
if (index < 0 || index > this.Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return this.Remove(this[index]);
}
/// <summary>
/// Creates a new list with specified element removed.
/// </summary>
/// <param name="node">The element to remove.</param>
public SeparatedSyntaxList<TNode> Remove(TNode node)
{
var nodesWithSeps = this.GetWithSeparators();
int index = nodesWithSeps.IndexOf(node);
if (index >= 0 && index <= nodesWithSeps.Count)
{
nodesWithSeps = nodesWithSeps.RemoveAt(index);
// remove separator too
if (index < nodesWithSeps.Count && nodesWithSeps[index].IsToken)
{
nodesWithSeps = nodesWithSeps.RemoveAt(index);
}
else if (index > 0 && nodesWithSeps[index - 1].IsToken)
{
nodesWithSeps = nodesWithSeps.RemoveAt(index - 1);
}
return new SeparatedSyntaxList<TNode>(nodesWithSeps);
}
return this;
}
/// <summary>
/// Creates a new list with the specified element replaced by the new node.
/// </summary>
/// <param name="nodeInList">The element to replace.</param>
/// <param name="newNode">The new node.</param>
public SeparatedSyntaxList<TNode> Replace(TNode nodeInList, TNode newNode)
{
if (newNode == null)
{
throw new ArgumentNullException(nameof(newNode));
}
var index = this.IndexOf(nodeInList);
if (index >= 0 && index < this.Count)
{
return new SeparatedSyntaxList<TNode>(this.GetWithSeparators().Replace(nodeInList, newNode));
}
throw new ArgumentOutOfRangeException(nameof(nodeInList));
}
/// <summary>
/// Creates a new list with the specified element replaced by the new nodes.
/// </summary>
/// <param name="nodeInList">The element to replace.</param>
/// <param name="newNodes">The new nodes.</param>
public SeparatedSyntaxList<TNode> ReplaceRange(TNode nodeInList, IEnumerable<TNode> newNodes)
{
if (newNodes == null)
{
throw new ArgumentNullException(nameof(newNodes));
}
var index = this.IndexOf(nodeInList);
if (index >= 0 && index < this.Count)
{
var newNodeList = newNodes.ToList();
if (newNodeList.Count == 0)
{
return this.Remove(nodeInList);
}
var listWithFirstReplaced = this.Replace(nodeInList, newNodeList[0]);
if (newNodeList.Count > 1)
{
newNodeList.RemoveAt(0);
return listWithFirstReplaced.InsertRange(index + 1, newNodeList);
}
return listWithFirstReplaced;
}
throw new ArgumentOutOfRangeException(nameof(nodeInList));
}
/// <summary>
/// Creates a new list with the specified separator token replaced with the new separator.
/// </summary>
/// <param name="separatorToken">The separator token to be replaced.</param>
/// <param name="newSeparator">The new separator token.</param>
public SeparatedSyntaxList<TNode> ReplaceSeparator(SyntaxToken separatorToken, SyntaxToken newSeparator)
{
var nodesWithSeps = this.GetWithSeparators();
var index = nodesWithSeps.IndexOf(separatorToken);
if (index < 0)
{
throw new ArgumentException("separatorToken");
}
if (newSeparator.RawKind != nodesWithSeps[index].RawKind ||
newSeparator.Language != nodesWithSeps[index].Language)
{
throw new ArgumentException("newSeparator");
}
return new SeparatedSyntaxList<TNode>(nodesWithSeps.Replace(separatorToken, newSeparator));
}
// for debugging
#pragma warning disable IDE0051 // Remove unused private members
private TNode[] Nodes
{
get { return this.ToArray(); }
}
private SyntaxNodeOrToken[] NodesWithSeparators
{
get { return _list.ToArray(); }
}
#pragma warning restore IDE0051 // Remove unused private members
#pragma warning disable RS0041 // uses oblivious reference types
public Enumerator GetEnumerator()
#pragma warning restore RS0041 // uses oblivious reference types
{
return new Enumerator(this);
}
IEnumerator<TNode> IEnumerable<TNode>.GetEnumerator()
{
if (this.Any())
{
return new EnumeratorImpl(this);
}
return SpecializedCollections.EmptyEnumerator<TNode>();
}
IEnumerator IEnumerable.GetEnumerator()
{
if (this.Any())
{
return new EnumeratorImpl(this);
}
return SpecializedCollections.EmptyEnumerator<TNode>();
}
public static implicit operator SeparatedSyntaxList<SyntaxNode>(SeparatedSyntaxList<TNode> nodes)
{
return new SeparatedSyntaxList<SyntaxNode>(nodes._list);
}
[Obsolete("This method is preserved for binary compatibility only. Use explicit cast instead.", error: true)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static SeparatedSyntaxList<TNode> op_Implicit(SeparatedSyntaxList<SyntaxNode> nodes)
{
return new SeparatedSyntaxList<TNode>(nodes._list);
}
public static explicit operator SeparatedSyntaxList<TNode>(SeparatedSyntaxList<SyntaxNode> nodes)
{
return new SeparatedSyntaxList<TNode>(nodes._list);
}
}
}
|