// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { public partial struct SeparatedSyntaxList : IEquatable>, IReadOnlyList 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.GetNodeSlot(index << 1); } } } throw new ArgumentOutOfRangeException(nameof(index)); } } /// /// Gets the separator at the given index in this list. /// /// The index. /// 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.GetSlot(index); Debug.Assert(green.IsToken); return new SyntaxToken(node.Parent, green, node.GetChildPosition(index), _list.index + index); } } throw new ArgumentOutOfRangeException(nameof(index)); } /// /// Returns the sequence of just the separator tokens. /// public IEnumerable GetSeparators() { return _list.Where(n => n.IsToken).Select(n => n.AsToken()); } /// /// The absolute span of the list elements in characters, including the leading and trailing trivia of the first and last elements. /// public TextSpan FullSpan { get { return _list.FullSpan; } } /// /// The absolute span of the list elements in characters, not including the leading and trailing trivia of the first and last elements. /// public TextSpan Span { get { return _list.Span; } } /// /// 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. /// /// /// 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. /// public override string ToString() { return _list.ToString(); } /// /// 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. /// /// /// 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. /// 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 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 predicate) { for (int i = this.Count - 1; i >= 0; i--) { if (predicate(this[i])) { return i; } } return -1; } public bool Any() { return _list.Any(); } public SyntaxNodeOrTokenList GetWithSeparators() { return _list; } public static bool operator ==(SeparatedSyntaxList left, SeparatedSyntaxList right) { return left.Equals(right); } public static bool operator !=(SeparatedSyntaxList left, SeparatedSyntaxList right) { return !left.Equals(right); } public bool Equals(SeparatedSyntaxList other) { return _list == other._list; } public override bool Equals(object obj) { return (obj is SeparatedSyntaxList) && Equals((SeparatedSyntaxList)obj); } public override int GetHashCode() { return _list.GetHashCode(); } /// /// Creates a new list with the specified node added to the end. /// /// The node to add. public SeparatedSyntaxList Add(TNode node) { return Insert(this.Count, node); } /// /// Creates a new list with the specified nodes added to the end. /// /// The nodes to add. public SeparatedSyntaxList AddRange(IEnumerable nodes) { return InsertRange(this.Count, nodes); } /// /// Creates a new list with the specified node inserted at the index. /// /// The index to insert at. /// The node to insert. public SeparatedSyntaxList Insert(int index, TNode node) { if (node == null) { throw new ArgumentNullException(nameof(node)); } return InsertRange(index, new[] { node }); } /// /// Creates a new list with the specified nodes inserted at the index. /// /// The index to insert at. /// The nodes to insert. public SeparatedSyntaxList InsertRange(int index, IEnumerable 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(); 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].IsNode) { var node = nodesWithSeps[insertionIndex].AsNode(); nodesToInsertWithSeparators.Add(node.Green.CreateSeparator(node)); // separator } return new SeparatedSyntaxList(nodesWithSeps.InsertRange(insertionIndex, nodesToInsertWithSeparators)); } private static bool KeepSeparatorWithPreviousNode(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) { if (tr.UnderlyingNode.IsTriviaWithEndOfLine()) { return true; } } return false; } /// /// Creates a new list with the element at the specified index removed. /// /// The index of the element to remove. public SeparatedSyntaxList RemoveAt(int index) { if (index < 0 || index > this.Count) { throw new ArgumentOutOfRangeException(nameof(index)); } return this.Remove(this[index]); } /// /// Creates a new list with specified element removed. /// /// The element to remove. public SeparatedSyntaxList 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(nodesWithSeps); } return this; } /// /// Creates a new list with the specified element replaced by the new node. /// /// The element to replace. /// The new node. public SeparatedSyntaxList 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(this.GetWithSeparators().Replace(nodeInList, newNode)); } throw new ArgumentOutOfRangeException(nameof(nodeInList)); } /// /// Creates a new list with the specified element replaced by the new nodes. /// /// The element to replace. /// The new nodes. public SeparatedSyntaxList ReplaceRange(TNode nodeInList, IEnumerable 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)); } /// /// Creates a new list with the specified separator token replaced with the new separator. /// /// The separator token to be replaced. /// The new separator token. public SeparatedSyntaxList 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(nodesWithSeps.Replace(separatorToken, newSeparator)); } // for debugging private TNode[] Nodes { get { return this.ToArray(); } } private SyntaxNodeOrToken[] NodesWithSeparators { get { return _list.ToArray(); } } public Enumerator GetEnumerator() { return new Enumerator(this); } IEnumerator IEnumerable.GetEnumerator() { if (this.Any()) { return new EnumeratorImpl(this); } return SpecializedCollections.EmptyEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { if (this.Any()) { return new EnumeratorImpl(this); } return SpecializedCollections.EmptyEnumerator(); } } }