// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #nullable enable using System; using System.Collections; using System.Collections.Generic; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; namespace ICSharpCode.Decompiler.CSharp.Syntax { /// /// Non-generic base of , letting the base /// manipulate a collection slot (remove an element by reference) without knowing its element type. /// public abstract class AstNodeCollection { internal abstract void AddNode(AstNode node); internal abstract void InsertNodeBefore(AstNode? existing, AstNode node); internal abstract void InsertNodeAfter(AstNode? existing, AstNode node); internal abstract bool RemoveNode(AstNode node); // The pattern matcher consumes a collection as an IReadOnlyList. The view is a separate // (cached) object rather than the collection itself, because having AstNodeCollection // implement IEnumerable as well as IEnumerable would make every LINQ call on a typed // collection ambiguous. private protected abstract int NodeCount { get; } private protected abstract INode NodeAt(int index); IReadOnlyList? nodeListView; internal IReadOnlyList AsNodeList() => nodeListView ??= new NodeListView(this); sealed class NodeListView : IReadOnlyList { readonly AstNodeCollection collection; public NodeListView(AstNodeCollection collection) => this.collection = collection; public INode this[int index] => collection.NodeAt(index); public int Count => collection.NodeCount; public IEnumerator GetEnumerator() { int count = collection.NodeCount; for (int i = 0; i < count; i++) yield return collection.NodeAt(i); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } /// /// Represents the children of an that occupy one collection slot. /// The elements are stored in a list owned by the collection; the parent's flattened child-index /// space contains them as a contiguous run, renumbered lazily by the parent after a mutation. /// public class AstNodeCollection : AstNodeCollection, ICollection, IReadOnlyList where T : AstNode { readonly AstNode parent; readonly Role role; readonly List list = new List(); public AstNodeCollection(AstNode parent, Role role) { this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); this.role = role ?? throw new ArgumentNullException(nameof(role)); } public int Count { get { return list.Count; } } private protected override int NodeCount => list.Count; private protected override INode NodeAt(int index) => list[index]; public T this[int index] { get { return list[index]; } set { T old = list[index]; if (old == value) return; ValidateNewChild(value); old.ClearParentAndIndex(); list[index] = value; value.SetParentAndRole(parent, role); parent.InvalidateChildIndices(); } } void ValidateNewChild(T child) { if (child == null) throw new ArgumentNullException(nameof(child)); if (child == parent) throw new ArgumentException("Cannot add a node to itself as a child.", nameof(child)); if (child.Parent != null) throw new ArgumentException("Node is already used in another tree.", nameof(child)); } public void Add(T element) { if (element == null) return; ValidateNewChild(element); list.Add(element); element.SetParentAndRole(parent, role); parent.InvalidateChildIndices(); } public void AddRange(IEnumerable nodes) { // Evaluate 'nodes' first, since it might change when we add the new children // Example: collection.AddRange(collection); if (nodes != null) { foreach (T node in nodes is ICollection ? nodes : new List(nodes)) Add(node); } } public void AddRange(T[] nodes) { // Fast overload for arrays - we don't need to create a copy if (nodes != null) { foreach (T node in nodes) Add(node); } } public void ReplaceWith(IEnumerable nodes) { // Evaluate 'nodes' first, since it might change when we call Clear() // Example: collection.ReplaceWith(collection); List? newNodes = nodes != null ? new List(nodes) : null; Clear(); if (newNodes != null) { foreach (T node in newNodes) Add(node); } } public void MoveTo(ICollection targetCollection) { if (targetCollection == null) throw new ArgumentNullException(nameof(targetCollection)); foreach (T node in list.ToArray()) { node.Remove(); targetCollection.Add(node); } } public bool Contains(T element) { return element != null && element.Parent == parent && element.RoleIndex == role.Index; } public int IndexOf(T element) { if (element == null || element.Parent != parent) return -1; return list.IndexOf(element); } public bool Remove(T element) { int index = IndexOf(element); if (index < 0) return false; list.RemoveAt(index); element.ClearParentAndIndex(); parent.InvalidateChildIndices(); return true; } internal override void AddNode(AstNode node) { Add((T)node); } internal override void InsertNodeBefore(AstNode? existing, AstNode node) { // 'existing' may belong to a different slot (e.g. the node after the last collection // element); in that case it is not found here and the new node is appended. if (existing is T e && IndexOf(e) >= 0) InsertBefore(e, (T)node); else Add((T)node); } internal override void InsertNodeAfter(AstNode? existing, AstNode node) { if (existing is T e && IndexOf(e) >= 0) InsertAfter(e, (T)node); else Insert(0, (T)node); } internal override bool RemoveNode(AstNode node) { return node is T typed && Remove(typed); } public void CopyTo(T[] array, int arrayIndex) { list.CopyTo(array, arrayIndex); } public void Clear() { foreach (T item in list) item.ClearParentAndIndex(); list.Clear(); parent.InvalidateChildIndices(); } public IEnumerable Detach() { T[] items = list.ToArray(); Clear(); return items; } /// /// Returns the first element for which the predicate returns true, /// or the null node (AstNode with IsNull=true) if no such object is found. /// public T FirstOrNullObject(Func? predicate = null) { foreach (T item in list) if (predicate == null || predicate(item)) return item; return role.NullObject; } /// /// Returns the last element for which the predicate returns true, /// or the null node (AstNode with IsNull=true) if no such object is found. /// public T LastOrNullObject(Func? predicate = null) { T result = role.NullObject; foreach (T item in list) if (predicate == null || predicate(item)) result = item; return result; } bool ICollection.IsReadOnly { get { return false; } } // Tolerates removing or replacing the current element during enumeration (the common // transform pattern), matching the previous linked-list collection's behavior. public IEnumerator GetEnumerator() { int pos = 0; while (pos < list.Count) { T cur = list[pos]; yield return cur; if (pos < list.Count && ReferenceEquals(list[pos], cur)) pos++; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #region Equals and GetHashCode implementation public override int GetHashCode() { return parent.GetHashCode() ^ role.GetHashCode(); } public override bool Equals(object? obj) { return obj is AstNodeCollection other && this.parent == other.parent && this.role == other.role; } #endregion internal bool DoMatch(AstNodeCollection other, Match match) { // Both collections are already the per-role child lists, so the matcher walks them by index. return Pattern.DoMatchCollection(AsNodeList(), other.AsNodeList(), match); } public void InsertAfter(T? existingItem, T newItem) { // A null existingItem yields IndexOf == -1, so the new item is inserted at the front. Insert(IndexOf(existingItem!) + 1, newItem); } public void InsertBefore(T existingItem, T newItem) { int index = IndexOf(existingItem); Insert(index < 0 ? list.Count : index, newItem); } void Insert(int index, T newItem) { if (newItem == null) return; ValidateNewChild(newItem); list.Insert(index, newItem); newItem.SetParentAndRole(parent, role); parent.InvalidateChildIndices(); } /// /// Applies the to all nodes in this collection. /// public void AcceptVisitor(IAstVisitor visitor) { foreach (T item in this) item.AcceptVisitor(visitor); } } }