// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using ICSharpCode.NRefactory.PatternMatching; using ICSharpCode.NRefactory.VB.Ast; namespace ICSharpCode.NRefactory.VB { public abstract class AstNode : AbstractAnnotatable, PatternMatching.INode { #region Null public static readonly AstNode Null = new NullAstNode (); sealed class NullAstNode : AstNode { 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 #region PatternPlaceholder public static implicit operator AstNode(PatternMatching.Pattern pattern) { return pattern != null ? new PatternPlaceholder(pattern) : null; } sealed class PatternPlaceholder : AstNode, PatternMatching.INode { readonly PatternMatching.Pattern child; public PatternPlaceholder(PatternMatching.Pattern child) { this.child = child; } public override S AcceptVisitor(IAstVisitor visitor, T data) { return visitor.VisitPatternPlaceholder(this, child, data); } protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) { return child.DoMatch(other, match); } bool PatternMatching.INode.DoMatchCollection(Role role, PatternMatching.INode pos, PatternMatching.Match match, PatternMatching.BacktrackingInfo backtrackingInfo) { return child.DoMatchCollection(role, pos, match, backtrackingInfo); } } #endregion AstNode parent; AstNode prevSibling; AstNode nextSibling; AstNode firstChild; AstNode lastChild; Role role = RootRole; public virtual bool IsNull { get { return false; } } public virtual TextLocation StartLocation { get { var child = firstChild; if (child == null) return TextLocation.Empty; return child.StartLocation; } } public virtual TextLocation EndLocation { get { var child = lastChild; if (child == null) return TextLocation.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) { Debug.Assert(cur.parent == this); // 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 all descendants of this node (including this node itself). /// public IEnumerable DescendantsAndSelf { get { return Utils.TreeTraversal.PreOrder(this, 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); } internal void AddChildUntyped(AstNode child, Role role) { 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 copy.CloneAnnotations(); return copy; } public override void AddAnnotation (object annotation) { if (this.IsNull) throw new InvalidOperationException("Cannot add annotations to the null node"); base.AddAnnotation (annotation); } public abstract S AcceptVisitor (IAstVisitor visitor, T data); #region Pattern Matching protected static bool MatchString(string name1, string name2) { return string.IsNullOrEmpty(name1) || string.Equals(name1, name2, StringComparison.OrdinalIgnoreCase); } protected static bool MatchStringXml(string name1, string name2) { return string.IsNullOrEmpty(name1) || string.Equals(name1, name2, StringComparison.Ordinal); } protected internal abstract bool DoMatch(AstNode other, PatternMatching.Match match); bool PatternMatching.INode.DoMatch(PatternMatching.INode other, PatternMatching.Match match) { AstNode o = other as AstNode; // try matching if other is null, or if other is an AstNode return (other == null || o != null) && DoMatch(o, match); } bool PatternMatching.INode.DoMatchCollection(Role role, PatternMatching.INode pos, PatternMatching.Match match, PatternMatching.BacktrackingInfo backtrackingInfo) { AstNode o = pos as AstNode; return (pos == null || o != null) && DoMatch (o, match); } PatternMatching.INode PatternMatching.INode.NextSibling { get { return nextSibling; } } PatternMatching.INode PatternMatching.INode.FirstChild { get { return firstChild; } } #endregion public AstNode GetNextNode () { if (NextSibling != null) return NextSibling; if (Parent != null) return Parent.GetNextNode (); return null; } public AstNode GetPrevNode () { if (PrevSibling != null) return PrevSibling; if (Parent != null) return Parent.GetPrevNode (); return null; } // filters all non VB nodes (comments, white spaces or pre processor directives) public AstNode GetVBNodeBefore (AstNode node) { var n = node.PrevSibling; while (n != null) { if (n.Role != Roles.Comment) return n; n = n.GetPrevNode (); } return null; } // 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", Ast.Identifier.Null); public static readonly Role XmlIdentifier = new Role("XmlIdentifier", Ast.XmlIdentifier.Null); public static readonly Role XmlLiteralString = new Role("XmlLiteralString", Ast.XmlLiteralString.Null); public static readonly Role Body = new Role("Body", Ast.BlockStatement.Null); public static readonly Role Parameter = new Role("Parameter"); public static readonly Role Argument = new Role("Argument", Ast.Expression.Null); public static readonly Role Type = new Role("Type", AstType.Null); public static readonly Role Expression = new Role("Expression", Ast.Expression.Null); public static readonly Role TargetExpression = new Role("Target", Ast.Expression.Null); public readonly static Role Condition = new Role("Condition", Ast.Expression.Null); // public static readonly Role TypeParameter = new Role("TypeParameter"); public static readonly Role TypeArgument = new Role("TypeArgument", AstType.Null); // 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", VBTokenNode.Null); // public static readonly Role InKeyword = new Role("InKeyword", VBTokenNode.Null); // some pre defined constants for most used punctuation public static readonly Role LPar = new Role("LPar", VBTokenNode.Null); public static readonly Role RPar = new Role("RPar", VBTokenNode.Null); public static readonly Role LBracket = new Role("LBracket", VBTokenNode.Null); public static readonly Role RBracket = new Role("RBracket", VBTokenNode.Null); public static readonly Role LBrace = new Role("LBrace", VBTokenNode.Null); public static readonly Role RBrace = new Role("RBrace", VBTokenNode.Null); public static readonly Role LChevron = new Role("LChevron", VBTokenNode.Null); public static readonly Role RChevron = new Role("RChevron", VBTokenNode.Null); public static readonly Role Comma = new Role("Comma", VBTokenNode.Null); public static readonly Role QuestionMark = new Role("QuestionMark", VBTokenNode.Null); public static readonly Role Dot = new Role("Dot", VBTokenNode.Null); public static readonly Role Semicolon = new Role("Semicolon", VBTokenNode.Null); public static readonly Role Assign = new Role("Assign", VBTokenNode.Null); public static readonly Role Colon = new Role("Colon", VBTokenNode.Null); public static readonly Role StatementTerminator = new Role("StatementTerminator", VBTokenNode.Null); // XML /// /// Text: < /// public static readonly Role XmlOpenTag = new Role("XmlOpenTag", VBTokenNode.Null); /// /// Text: > /// public static readonly Role XmlCloseTag = new Role("XmlOpenTag", VBTokenNode.Null); public static readonly Role Comment = new Role("Comment"); } } }