// // AstNode.cs // // Author: // Mike Krüger // // Copyright (c) 2009 Novell, Inc (http://www.novell.com) // // 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. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using ICSharpCode.NRefactory.CSharp.PatternMatching; namespace ICSharpCode.NRefactory.CSharp { public abstract class AstNode { #region Null public static readonly AstNode Null = new NullAstNode (); sealed class NullAstNode : AstNode { public override NodeType NodeType { get { return NodeType.Unknown; } } public override bool IsNull { get { return true; } } public override S AcceptVisitor (IAstVisitor visitor, T data) { return default (S); } protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) { return other == null || other.IsNull; } } #endregion AstNode parent; AstNode prevSibling; AstNode nextSibling; AstNode firstChild; AstNode lastChild; Role role = RootRole; public abstract NodeType NodeType { get; } public virtual bool IsNull { get { return false; } } public virtual AstLocation StartLocation { get { var child = firstChild; if (child == null) return AstLocation.Empty; return child.StartLocation; } } public virtual AstLocation EndLocation { get { var child = lastChild; if (child == null) return AstLocation.Empty; return child.EndLocation; } } public AstNode Parent { get { return parent; } } public Role Role { get { return role; } } public AstNode NextSibling { get { return nextSibling; } } public AstNode PrevSibling { get { return prevSibling; } } public AstNode FirstChild { get { return firstChild; } } public AstNode LastChild { get { return lastChild; } } public IEnumerable Children { get { AstNode next; for (AstNode cur = firstChild; cur != null; cur = next) { // Remember next before yielding cur. // This allows removing/replacing nodes while iterating through the list. next = cur.nextSibling; yield return cur; } } } /// /// Gets the ancestors of this node (excluding this node itself) /// public IEnumerable Ancestors { get { for (AstNode cur = parent; cur != null; cur = cur.parent) { yield return cur; } } } /// /// Gets all descendants of this node (excluding this node itself). /// public IEnumerable Descendants { get { return Utils.TreeTraversal.PreOrder(this.Children, n => n.Children); } } /// /// Gets the first child with the specified role. /// Returns the role's null object if the child is not found. /// public T GetChildByRole(Role role) where T : AstNode { if (role == null) throw new ArgumentNullException("role"); for (var cur = firstChild; cur != null; cur = cur.nextSibling) { if (cur.role == role) return (T)cur; } return role.NullObject; } public AstNodeCollection GetChildrenByRole(Role role) where T : AstNode { return new AstNodeCollection(this, role); } protected void SetChildByRole(Role role, T newChild) where T : AstNode { AstNode oldChild = GetChildByRole(role); if (oldChild.IsNull) AddChild(newChild, role); else oldChild.ReplaceWith(newChild); } public void AddChild(T child, Role role) where T : AstNode { if (role == null) throw new ArgumentNullException("role"); if (child == null || child.IsNull) return; if (this.IsNull) throw new InvalidOperationException("Cannot add children to null nodes"); if (child.parent != null) throw new ArgumentException ("Node is already used in another tree.", "child"); AddChildUnsafe(child, role); } /// /// Adds a child without performing any safety checks. /// void AddChildUnsafe(AstNode child, Role role) { child.parent = this; child.role = role; if (firstChild == null) { lastChild = firstChild = child; } else { lastChild.nextSibling = child; child.prevSibling = lastChild; lastChild = child; } } public void InsertChildBefore(AstNode nextSibling, T child, Role role) where T : AstNode { if (role == null) throw new ArgumentNullException("role"); if (nextSibling == null || nextSibling.IsNull) { AddChild(child, role); return; } if (child == null || child.IsNull) return; if (child.parent != null) throw new ArgumentException ("Node is already used in another tree.", "child"); if (nextSibling.parent != this) throw new ArgumentException ("NextSibling is not a child of this node.", "nextSibling"); // No need to test for "Cannot add children to null nodes", // as there isn't any valid nextSibling in null nodes. InsertChildBeforeUnsafe(nextSibling, child, role); } void InsertChildBeforeUnsafe(AstNode nextSibling, AstNode child, Role role) { child.parent = this; child.role = role; child.nextSibling = nextSibling; child.prevSibling = nextSibling.prevSibling; if (nextSibling.prevSibling != null) { Debug.Assert(nextSibling.prevSibling.nextSibling == nextSibling); nextSibling.prevSibling.nextSibling = child; } else { Debug.Assert(firstChild == nextSibling); firstChild = child; } nextSibling.prevSibling = child; } public void InsertChildAfter(AstNode prevSibling, T child, Role role) where T : AstNode { InsertChildBefore((prevSibling == null || prevSibling.IsNull) ? firstChild : prevSibling.nextSibling, child, role); } /// /// Removes this node from its parent. /// public void Remove() { if (parent != null) { if (prevSibling != null) { Debug.Assert(prevSibling.nextSibling == this); prevSibling.nextSibling = nextSibling; } else { Debug.Assert(parent.firstChild == this); parent.firstChild = nextSibling; } if (nextSibling != null) { Debug.Assert(nextSibling.prevSibling == this); nextSibling.prevSibling = prevSibling; } else { Debug.Assert(parent.lastChild == this); parent.lastChild = prevSibling; } parent = null; role = Roles.Root; prevSibling = null; nextSibling = null; } } /// /// Replaces this node with the new node. /// public void ReplaceWith(AstNode newNode) { if (newNode == null || newNode.IsNull) { Remove(); return; } if (newNode == this) return; // nothing to do... if (parent == null) { throw new InvalidOperationException(this.IsNull ? "Cannot replace the null nodes" : "Cannot replace the root node"); } // Because this method doesn't statically check the new node's type with the role, // we perform a runtime test: if (!role.IsValid(newNode)) { throw new ArgumentException (string.Format("The new node '{0}' is not valid in the role {1}", newNode.GetType().Name, role.ToString()), "newNode"); } if (newNode.parent != null) { // newNode is used within this tree? if (newNode.Ancestors.Contains(this)) { // e.g. "parenthesizedExpr.ReplaceWith(parenthesizedExpr.Expression);" // enable automatic removal newNode.Remove(); } else { throw new ArgumentException ("Node is already used in another tree.", "newNode"); } } newNode.parent = parent; newNode.role = role; newNode.prevSibling = prevSibling; newNode.nextSibling = nextSibling; if (parent != null) { if (prevSibling != null) { Debug.Assert(prevSibling.nextSibling == this); prevSibling.nextSibling = newNode; } else { Debug.Assert(parent.firstChild == this); parent.firstChild = newNode; } if (nextSibling != null) { Debug.Assert(nextSibling.prevSibling == this); nextSibling.prevSibling = newNode; } else { Debug.Assert(parent.lastChild == this); parent.lastChild = newNode; } parent = null; prevSibling = null; nextSibling = null; role = Roles.Root; } } public AstNode ReplaceWith(Func replaceFunction) { if (replaceFunction == null) throw new ArgumentNullException("replaceFunction"); if (parent == null) { throw new InvalidOperationException(this.IsNull ? "Cannot replace the null nodes" : "Cannot replace the root node"); } AstNode oldParent = parent; AstNode oldSuccessor = nextSibling; Role oldRole = role; Remove(); AstNode replacement = replaceFunction(this); if (oldSuccessor != null && oldSuccessor.parent != oldParent) throw new InvalidOperationException("replace function changed nextSibling of node being replaced?"); if (!(replacement == null || replacement.IsNull)) { if (replacement.parent != null) throw new InvalidOperationException("replace function must return the root of a tree"); if (!oldRole.IsValid(replacement)) { throw new InvalidOperationException (string.Format("The new node '{0}' is not valid in the role {1}", replacement.GetType().Name, oldRole.ToString())); } if (oldSuccessor != null) oldParent.InsertChildBeforeUnsafe(oldSuccessor, replacement, oldRole); else oldParent.AddChildUnsafe(replacement, oldRole); } return replacement; } /// /// Clones the whole subtree starting at this AST node. /// /// Annotations are copied over to the new nodes; and any annotations implementating ICloneable will be cloned. public AstNode Clone() { AstNode copy = (AstNode)MemberwiseClone(); // First, reset the shallow pointer copies copy.parent = null; copy.role = Roles.Root; copy.firstChild = null; copy.lastChild = null; copy.prevSibling = null; copy.nextSibling = null; // Then perform a deep copy: for (AstNode cur = firstChild; cur != null; cur = cur.nextSibling) { copy.AddChildUnsafe(cur.Clone(), cur.role); } // Finally, clone the annotation, if necessary ICloneable annotations = copy.annotations as ICloneable; // read from copy (for thread-safety) if (annotations != null) copy.annotations = annotations.Clone(); return copy; } #region Annotation support // Annotations: points either null (no annotations), to the single annotation, // or to an AnnotationList. // Once it is pointed at an AnnotationList, it will never change (this allows thread-safety support by locking the list) object annotations; sealed class AnnotationList : List, ICloneable { // There are two uses for this custom list type: // 1) it's private, and thus (unlike List) cannot be confused with real annotations // 2) It allows us to simplify the cloning logic by making the list behave the same as a clonable annotation. public AnnotationList(int initialCapacity) : base(initialCapacity) { } public object Clone() { lock (this) { AnnotationList copy = new AnnotationList(this.Count); for (int i = 0; i < this.Count; i++) { object obj = this[i]; ICloneable c = obj as ICloneable; copy.Add(c != null ? c.Clone() : obj); } return copy; } } } public void AddAnnotation(object annotation) { if (annotation == null) throw new ArgumentNullException("annotation"); if (this.IsNull) throw new InvalidOperationException("Cannot add annotations to the null node"); retry: // Retry until successful object oldAnnotation = Interlocked.CompareExchange(ref this.annotations, annotation, null); if (oldAnnotation == null) { return; // we successfully added a single annotation } AnnotationList list = oldAnnotation as AnnotationList; if (list == null) { // we need to transform the old annotation into a list list = new AnnotationList(4); list.Add(oldAnnotation); list.Add(annotation); if (Interlocked.CompareExchange(ref this.annotations, list, oldAnnotation) != oldAnnotation) { // the transformation failed (some other thread wrote to this.annotations first) goto retry; } } else { // once there's a list, use simple locking lock (list) { list.Add(annotation); } } } public void RemoveAnnotations() where T : class { retry: // Retry until successful object oldAnnotations = this.annotations; AnnotationList list = oldAnnotations as AnnotationList; if (list != null) { lock (list) list.RemoveAll(obj => obj is T); } else if (oldAnnotations is T) { if (Interlocked.CompareExchange(ref this.annotations, null, oldAnnotations) != oldAnnotations) { // Operation failed (some other thread wrote to this.annotations first) goto retry; } } } public void RemoveAnnotations(Type type) { if (type == null) throw new ArgumentNullException("type"); retry: // Retry until successful object oldAnnotations = this.annotations; AnnotationList list = oldAnnotations as AnnotationList; if (list != null) { lock (list) list.RemoveAll(obj => type.IsInstanceOfType(obj)); } else if (type.IsInstanceOfType(oldAnnotations)) { if (Interlocked.CompareExchange(ref this.annotations, null, oldAnnotations) != oldAnnotations) { // Operation failed (some other thread wrote to this.annotations first) goto retry; } } } public T Annotation() where T: class { object annotations = this.annotations; AnnotationList list = annotations as AnnotationList; if (list != null) { lock (list) { foreach (object obj in list) { T t = obj as T; if (t != null) return t; } return null; } } else { return annotations as T; } } public object Annotation(Type type) { if (type == null) throw new ArgumentNullException("type"); object annotations = this.annotations; AnnotationList list = annotations as AnnotationList; if (list != null) { lock (list) { foreach (object obj in list) { if (type.IsInstanceOfType(obj)) return obj; } } } else { if (type.IsInstanceOfType(annotations)) return annotations; } return null; } public IEnumerable Annotations() where T: class { object annotations = this.annotations; AnnotationList list = annotations as AnnotationList; if (list != null) { List result = new List(); lock (list) { foreach (object obj in list) { T t = obj as T; if (t != null) result.Add(t); } } return result; } else { T t = annotations as T; if (t != null) return new T[] { t }; else return Enumerable.Empty(); } } public IEnumerable Annotations(Type type) { if (type == null) throw new ArgumentNullException("type"); object annotations = this.annotations; AnnotationList list = annotations as AnnotationList; if (list != null) { List result = new List(); lock (list) { foreach (object obj in list) { if (type.IsInstanceOfType(obj)) result.Add(obj); } } return result; } else { if (type.IsInstanceOfType(annotations)) return new object[] { annotations }; else return Enumerable.Empty(); } } #endregion public abstract S AcceptVisitor (IAstVisitor visitor, T data); #region Pattern Matching public Match Match(AstNode other) { Match match = new Match(); if (DoMatch(other, match)) return match; else return null; } protected static bool MatchString(string name1, string name2) { return string.IsNullOrEmpty(name1) || name1 == name2; } protected internal abstract bool DoMatch(AstNode other, Match match); #endregion // the Root role must be available when creating the null nodes, so we can't put it in the Roles class static readonly Role RootRole = new Role("Root"); public static class Roles { /// /// Root of an abstract syntax tree. /// public static readonly Role Root = RootRole; // some pre defined constants for common roles public static readonly Role Identifier = new Role("Identifier", CSharp.Identifier.Null); public static readonly Role Body = new Role("Body", CSharp.BlockStatement.Null); public static readonly Role Parameter = new Role("Parameter"); public static readonly Role Argument = new Role("Argument", CSharp.Expression.Null); public static readonly Role Type = new Role("Type", CSharp.AstType.Null); public static readonly Role Expression = new Role("Expression", CSharp.Expression.Null); public static readonly Role TargetExpression = new Role("Target", CSharp.Expression.Null); public readonly static Role Condition = new Role("Condition", CSharp.Expression.Null); public static readonly Role TypeParameter = new Role("TypeParameter"); public static readonly Role TypeArgument = new Role("TypeArgument", CSharp.AstType.Null); public readonly static Role Constraint = new Role("Constraint"); public static readonly Role Variable = new Role("Variable"); public static readonly Role EmbeddedStatement = new Role("EmbeddedStatement", CSharp.Statement.Null); public static readonly Role Keyword = new Role("Keyword", CSharpTokenNode.Null); public static readonly Role InKeyword = new Role("InKeyword", CSharpTokenNode.Null); // some pre defined constants for most used punctuation public static readonly Role LPar = new Role("LPar", CSharpTokenNode.Null); public static readonly Role RPar = new Role("RPar", CSharpTokenNode.Null); public static readonly Role LBracket = new Role("LBracket", CSharpTokenNode.Null); public static readonly Role RBracket = new Role("RBracket", CSharpTokenNode.Null); public static readonly Role LBrace = new Role("LBrace", CSharpTokenNode.Null); public static readonly Role RBrace = new Role("RBrace", CSharpTokenNode.Null); public static readonly Role LChevron = new Role("LChevron", CSharpTokenNode.Null); public static readonly Role RChevron = new Role("RChevron", CSharpTokenNode.Null); public static readonly Role Comma = new Role("Comma", CSharpTokenNode.Null); public static readonly Role Dot = new Role("Dot", CSharpTokenNode.Null); public static readonly Role Semicolon = new Role("Semicolon", CSharpTokenNode.Null); public static readonly Role Assign = new Role("Assign", CSharpTokenNode.Null); public static readonly Role Colon = new Role("Colon", CSharpTokenNode.Null); public static readonly Role Comment = new Role("Comment"); } } }