// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.PrettyPrinter;
namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver
{
///
/// Allows converting code snippets between C# and VB.
/// This class isn't used by SharpDevelop itself (because it doesn't support projects).
/// It works by creating a dummy project for the file to convert with a set of default references.
///
public class CodeSnippetConverter
{
///
/// Project-wide imports to add to all files when converting VB to C#.
///
public IList DefaultImportsToAdd = new List { "Microsoft.VisualBasic", "System", "System.Collections", "System.Collections.Generic", "System.Data", "System.Diagnostics" };
///
/// Imports to remove (because they will become project-wide imports) when converting C# to VB.
///
public IList DefaultImportsToRemove = new List { "Microsoft.VisualBasic", "System" };
///
/// References project contents, for resolving type references during the conversion.
///
public IList ReferencedContents = new List();
DefaultProjectContent project;
List specials;
CompilationUnit compilationUnit;
ParseInformation parseInfo;
bool wasExpression;
#region Parsing
INode Parse(SupportedLanguage sourceLanguage, string sourceCode, out string error)
{
project = new DefaultProjectContent();
project.ReferencedContents.AddRange(ReferencedContents);
if (sourceLanguage == SupportedLanguage.VBNet) {
project.Language = LanguageProperties.VBNet;
project.DefaultImports = new DefaultUsing(project);
project.DefaultImports.Usings.AddRange(DefaultImportsToAdd);
} else {
project.Language = LanguageProperties.CSharp;
}
SnippetParser parser = new SnippetParser(sourceLanguage);
INode result = parser.Parse(sourceCode);
error = parser.Errors.ErrorOutput;
specials = parser.Specials;
if (parser.Errors.Count != 0)
return null;
wasExpression = parser.SnippetType == SnippetType.Expression;
if (wasExpression) {
// Special case 'Expression': expressions may be replaced with other statements in the AST by the ConvertVisitor,
// but we need to return a 'stable' node so that the correct transformed AST is returned.
// Thus, we wrap any expressions into a statement block.
result = MakeBlockFromExpression((Expression)result);
}
// now create a dummy compilation unit around the snippet result
switch (parser.SnippetType) {
case SnippetType.CompilationUnit:
compilationUnit = (CompilationUnit)result;
break;
case SnippetType.Expression:
case SnippetType.Statements:
compilationUnit = MakeCompilationUnitFromTypeMembers(
MakeMethodFromBlock(
(BlockStatement)result
));
break;
case SnippetType.TypeMembers:
compilationUnit = MakeCompilationUnitFromTypeMembers(result.Children);
break;
default:
throw new NotSupportedException("Unknown snippet type: " + parser.SnippetType);
}
// convert NRefactory CU in DOM CU
NRefactoryASTConvertVisitor visitor = new NRefactoryASTConvertVisitor(project, sourceLanguage);
visitor.VisitCompilationUnit(compilationUnit, null);
visitor.Cu.FileName = sourceLanguage == SupportedLanguage.CSharp ? "a.cs" : "a.vb";
// and register the compilation unit in the DOM
foreach (IClass c in visitor.Cu.Classes) {
project.AddClassToNamespaceList(c);
}
parseInfo = new ParseInformation(visitor.Cu);
return result;
}
///
/// Unpacks the expression from a statement block; if it was wrapped earlier.
///
INode UnpackExpression(INode node)
{
if (wasExpression) {
BlockStatement block = node as BlockStatement;
if (block != null && block.Children.Count == 1) {
ExpressionStatement es = block.Children[0] as ExpressionStatement;
if (es != null)
return es.Expression;
}
}
return node;
}
BlockStatement MakeBlockFromExpression(Expression expr)
{
return new BlockStatement {
Children = {
new ExpressionStatement(expr)
},
StartLocation = expr.StartLocation,
EndLocation = expr.EndLocation
};
}
INode[] MakeMethodFromBlock(BlockStatement block)
{
return new INode[] {
new MethodDeclaration {
Name = "DummyMethodForConversion",
Body = block,
StartLocation = block.StartLocation,
EndLocation = block.EndLocation
}
};
}
CompilationUnit MakeCompilationUnitFromTypeMembers(IList members)
{
TypeDeclaration type = new TypeDeclaration(Modifiers.None, null) {
Name = "DummyTypeForConversion",
StartLocation = members[0].StartLocation,
EndLocation = GetEndLocation(members[members.Count - 1])
};
type.Children.AddRange(members);
return new CompilationUnit {
Children = {
type
}
};
}
Location GetEndLocation(INode node)
{
// workaround: MethodDeclaration.EndLocation is the end of the method header,
// but for the end of the dummy class we need the body end
MethodDeclaration method = node as MethodDeclaration;
if (method != null && !method.Body.IsNull)
return method.Body.EndLocation;
else
return node.EndLocation;
}
#endregion
public string CSharpToVB(string input, out string errors)
{
INode node = Parse(SupportedLanguage.CSharp, input, out errors);
if (node == null)
return null;
// apply conversion logic:
compilationUnit.AcceptVisitor(
new CSharpToVBNetConvertVisitor(project, parseInfo) {
DefaultImportsToRemove = DefaultImportsToRemove,
},
null);
PreprocessingDirective.CSharpToVB(specials);
return CreateCode(UnpackExpression(node), new VBNetOutputVisitor());
}
public string VBToCSharp(string input, out string errors)
{
INode node = Parse(SupportedLanguage.VBNet, input, out errors);
if (node == null)
return null;
// apply conversion logic:
compilationUnit.AcceptVisitor(
new VBNetToCSharpConvertVisitor(project, parseInfo),
null);
PreprocessingDirective.VBToCSharp(specials);
return CreateCode(UnpackExpression(node), new CSharpOutputVisitor());
}
string CreateCode(INode node, IOutputAstVisitor outputVisitor)
{
using (SpecialNodesInserter.Install(specials, outputVisitor)) {
node.AcceptVisitor(outputVisitor, null);
}
return outputVisitor.Text;
}
}
}