// 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.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.NRefactory.CSharp;
using ICSharpCode.NRefactory.CSharp.PatternMatching;
using Mono.Cecil;
namespace ICSharpCode.Decompiler.Ast.Transforms
{
///
/// Finds the expanded form of using statements using pattern matching and replaces it with a UsingStatement.
///
public sealed class PatternStatementTransform : ContextTrackingVisitor, IAstTransform
{
public PatternStatementTransform(DecompilerContext context) : base(context)
{
}
#region Visitor Overrides
protected override AstNode VisitChildren(AstNode node, object data)
{
// Go through the children, and keep visiting a node as long as it changes.
// Because some transforms delete/replace nodes before and after the node being transformed, we rely
// on the transform's return value to know where we need to keep iterating.
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
AstNode oldChild;
do {
oldChild = child;
child = child.AcceptVisitor(this, data);
Debug.Assert(child != null && child.Parent == node);
} while (child != oldChild);
}
return node;
}
public override AstNode VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, object data)
{
AstNode result;
if (context.Settings.UsingStatement) {
result = TransformUsings(variableDeclarationStatement);
if (result != null)
return result;
}
result = TransformFor(variableDeclarationStatement);
if (result != null)
return result;
if (context.Settings.LockStatement) {
result = TransformLock(variableDeclarationStatement);
if (result != null)
return result;
}
return base.VisitVariableDeclarationStatement(variableDeclarationStatement, data);
}
public override AstNode VisitUsingStatement(UsingStatement usingStatement, object data)
{
if (context.Settings.ForEachStatement) {
AstNode result = TransformForeach(usingStatement);
if (result != null)
return result;
}
return base.VisitUsingStatement(usingStatement, data);
}
public override AstNode VisitWhileStatement(WhileStatement whileStatement, object data)
{
return TransformDoWhile(whileStatement) ?? base.VisitWhileStatement(whileStatement, data);
}
public override AstNode VisitIfElseStatement(IfElseStatement ifElseStatement, object data)
{
if (context.Settings.SwitchStatementOnString) {
AstNode result = TransformSwitchOnString(ifElseStatement);
if (result != null)
return result;
}
return base.VisitIfElseStatement(ifElseStatement, data);
}
public override AstNode VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, object data)
{
if (context.Settings.AutomaticProperties) {
AstNode result = TransformAutomaticProperties(propertyDeclaration);
if (result != null)
return result;
}
return base.VisitPropertyDeclaration(propertyDeclaration, data);
}
public override AstNode VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration, object data)
{
// first apply transforms to the accessor bodies
base.VisitCustomEventDeclaration(eventDeclaration, data);
if (context.Settings.AutomaticEvents) {
AstNode result = TransformAutomaticEvents(eventDeclaration);
if (result != null)
return result;
}
return eventDeclaration;
}
public override AstNode VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data)
{
return TransformDestructor(methodDeclaration) ?? base.VisitMethodDeclaration(methodDeclaration, data);
}
public override AstNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement, object data)
{
return TransformTryCatchFinally(tryCatchStatement) ?? base.VisitTryCatchStatement(tryCatchStatement, data);
}
#endregion
///
/// $type $variable = $initializer;
///
static readonly AstNode variableDeclPattern = new VariableDeclarationStatement {
Type = new AnyNode("type"),
Variables = {
new NamedNode(
"variable",
new VariableInitializer {
Initializer = new AnyNode("initializer")
}
)
}
};
///
/// Variable declaration without initializer.
///
static readonly AstNode simpleVariableDefinition = new VariableDeclarationStatement {
Type = new AnyNode(),
Variables = {
new VariableInitializer() // any name but no initializer
}
};
#region using
static readonly AstNode usingTryCatchPattern = new TryCatchStatement {
TryBlock = new AnyNode("body"),
FinallyBlock = new BlockStatement {
new Choice {
{ "valueType",
new ExpressionStatement(new NamedNode("ident", new IdentifierExpression()).ToExpression().Invoke("Dispose"))
},
{ "referenceType",
new IfElseStatement {
Condition = new BinaryOperatorExpression(
new NamedNode("ident", new IdentifierExpression()),
BinaryOperatorType.InEquality,
new NullReferenceExpression()
),
TrueStatement = new BlockStatement {
new ExpressionStatement(new Backreference("ident").ToExpression().Invoke("Dispose"))
}
}
}
}.ToStatement()
}
};
public UsingStatement TransformUsings(VariableDeclarationStatement node)
{
Match m1 = variableDeclPattern.Match(node);
if (m1 == null) return null;
AstNode tryCatch = node.NextSibling;
while (simpleVariableDefinition.Match(tryCatch) != null)
tryCatch = tryCatch.NextSibling;
Match m2 = usingTryCatchPattern.Match(tryCatch);
if (m2 == null) return null;
if (m1.Get("variable").Single().Name == m2.Get("ident").Single().Identifier) {
if (m2.Has("valueType")) {
// if there's no if(x!=null), then it must be a value type
TypeReference tr = m1.Get("type").Single().Annotation();
if (tr == null || !tr.IsValueType)
return null;
}
BlockStatement body = m2.Get("body").Single();
UsingStatement usingStatement = new UsingStatement();
usingStatement.ResourceAcquisition = node.Detach();
usingStatement.EmbeddedStatement = body.Detach();
tryCatch.ReplaceWith(usingStatement);
return usingStatement;
}
return null;
}
#endregion
#region foreach
static readonly UsingStatement foreachPattern = new UsingStatement {
ResourceAcquisition = new VariableDeclarationStatement {
Type = new AnyNode("enumeratorType"),
Variables = {
new NamedNode(
"enumeratorVariable",
new VariableInitializer {
Initializer = new AnyNode("collection").ToExpression().Invoke("GetEnumerator")
}
)
}
},
EmbeddedStatement = new Choice {
// There are two forms of the foreach statement:
// one where the item variable is declared inside the loop,
// and one where it is declared outside of the loop.
// In the former case, we can apply the foreach pattern only if the variable wasn't captured.
{ "itemVariableInsideLoop",
new BlockStatement {
new WhileStatement {
Condition = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Invoke("MoveNext"),
EmbeddedStatement = new BlockStatement {
new VariableDeclarationStatement {
Type = new AnyNode("itemType"),
Variables = {
new NamedNode(
"itemVariable",
new VariableInitializer {
Initializer = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Member("Current")
}
)
}
},
new Repeat(new AnyNode("statement")).ToStatement()
}
}
}
},
{ "itemVariableOutsideLoop",
new BlockStatement {
new VariableDeclarationStatement {
Type = new AnyNode("itemType"),
Variables = {
new NamedNode("itemVariable", new VariableInitializer())
}
},
new WhileStatement {
Condition = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Invoke("MoveNext"),
EmbeddedStatement = new BlockStatement {
new AssignmentExpression {
Left = new IdentifierExpressionBackreference("itemVariable"),
Operator = AssignmentOperatorType.Assign,
Right = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Member("Current")
},
new Repeat(new AnyNode("statement")).ToStatement()
}
}
}
}
}.ToStatement()
};
public ForeachStatement TransformForeach(UsingStatement node)
{
Match m = foreachPattern.Match(node);
if (m == null)
return null;
VariableInitializer enumeratorVar = m.Get("enumeratorVariable").Single();
VariableInitializer itemVar = m.Get("itemVariable").Single();
if (m.Has("itemVariableInsideLoop") && itemVar.Annotation() != null) {
// cannot move captured variables out of loops
return null;
}
BlockStatement newBody = new BlockStatement();
foreach (Statement stmt in m.Get("statement"))
newBody.Add(stmt.Detach());
ForeachStatement foreachStatement = new ForeachStatement {
VariableType = m.Get("itemType").Single().Detach(),
VariableName = itemVar.Name,
InExpression = m.Get("collection").Single().Detach(),
EmbeddedStatement = newBody
};
node.ReplaceWith(foreachStatement);
return foreachStatement;
}
#endregion
#region for
static readonly WhileStatement forPattern = new WhileStatement {
Condition = new BinaryOperatorExpression {
Left = new NamedNode("ident", new IdentifierExpression()),
Operator = BinaryOperatorType.Any,
Right = new AnyNode("endExpr")
},
EmbeddedStatement = new BlockStatement {
Statements = {
new Repeat(new AnyNode("statement")),
new NamedNode(
"increment",
new ExpressionStatement(
new AssignmentExpression {
Left = new Backreference("ident"),
Operator = AssignmentOperatorType.Any,
Right = new AnyNode()
}))
}
}};
public ForStatement TransformFor(VariableDeclarationStatement node)
{
Match m1 = variableDeclPattern.Match(node);
if (m1 == null) return null;
AstNode next = node.NextSibling;
while (simpleVariableDefinition.Match(next) != null)
next = next.NextSibling;
Match m2 = forPattern.Match(next);
if (m2 == null) return null;
// ensure the variable in the for pattern is the same as in the declaration
if (m1.Get("variable").Single().Name != m2.Get("ident").Single().Identifier)
return null;
WhileStatement loop = (WhileStatement)next;
node.Remove();
BlockStatement newBody = new BlockStatement();
foreach (Statement stmt in m2.Get("statement"))
newBody.Add(stmt.Detach());
ForStatement forStatement = new ForStatement();
forStatement.Initializers.Add(node);
forStatement.Condition = loop.Condition.Detach();
forStatement.Iterators.Add(m2.Get("increment").Single().Detach());
forStatement.EmbeddedStatement = newBody;
loop.ReplaceWith(forStatement);
return forStatement;
}
#endregion
#region doWhile
static readonly WhileStatement doWhilePattern = new WhileStatement {
Condition = new PrimitiveExpression(true),
EmbeddedStatement = new BlockStatement {
Statements = {
new Repeat(new AnyNode("statement")),
new IfElseStatement {
Condition = new AnyNode("condition"),
TrueStatement = new BlockStatement { new BreakStatement() }
}
}
}};
public DoWhileStatement TransformDoWhile(WhileStatement whileLoop)
{
Match m = doWhilePattern.Match(whileLoop);
if (m != null) {
DoWhileStatement doLoop = new DoWhileStatement();
doLoop.Condition = new UnaryOperatorExpression(UnaryOperatorType.Not, m.Get("condition").Single().Detach());
doLoop.Condition.AcceptVisitor(new PushNegation(), null);
BlockStatement block = (BlockStatement)whileLoop.EmbeddedStatement;
block.Statements.Last().Remove(); // remove if statement
doLoop.EmbeddedStatement = block.Detach();
whileLoop.ReplaceWith(doLoop);
// we may have to extract variable definitions out of the loop if they were used in the condition:
foreach (var varDecl in block.Statements.OfType()) {
VariableInitializer v = varDecl.Variables.Single();
if (doLoop.Condition.DescendantsAndSelf.OfType().Any(i => i.Identifier == v.Name)) {
AssignmentExpression assign = new AssignmentExpression(new IdentifierExpression(v.Name), v.Initializer.Detach());
// move annotations from v to assign:
assign.CopyAnnotationsFrom(v);
v.RemoveAnnotations