diff --git a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs index 7ea5c0b02..9235e7698 100644 --- a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs +++ b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs @@ -12,6 +12,7 @@ namespace ICSharpCode.Decompiler public CancellationToken CancellationToken; public TypeDefinition CurrentType; public MethodDefinition CurrentMethod; + public DecompilerSettings Settings; public DecompilerContext Clone() { diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index c0fcabab3..7684b1702 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -105,6 +105,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef) { + if (!context.Settings.AnonymousMethods) + return false; // anonymous method decompilation is disabled + // Anonymous methods are defined in the same assembly, so there's no need to Resolve(). MethodDefinition method = methodRef as MethodDefinition; if (!IsAnonymousMethod(context, method)) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs new file mode 100644 index 000000000..53736311a --- /dev/null +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -0,0 +1,55 @@ +// 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.ComponentModel; + +namespace ICSharpCode.Decompiler +{ + /// + /// Settings for the decompiler. + /// + public class DecompilerSettings : INotifyPropertyChanged + { + bool anonymousMethods = true; + + /// + /// Decompile anonymous methods/lambdas. + /// + public bool AnonymousMethods { + get { return anonymousMethods; } + set { + if (anonymousMethods != value) { + anonymousMethods = value; + OnPropertyChanged("AnonymousMethods"); + } + } + } + + bool yieldReturn = true; + + /// + /// Decompile enumerators. + /// + public bool YieldReturn { + get { return yieldReturn; } + set { + if (yieldReturn != value) { + yieldReturn = value; + OnPropertyChanged("YieldReturn"); + } + } + } + + public event EventHandler YieldReturnChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 3896d43f1..104543360 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -66,6 +66,7 @@ + @@ -95,6 +96,7 @@ + diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs new file mode 100644 index 000000000..b1ec1b78b --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs @@ -0,0 +1,15 @@ +// 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; + +namespace ICSharpCode.Decompiler.ILAst +{ + public class YieldReturnDecompiler + { + // For a description on the code generated by the C# compiler for yield return: + // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx + + // not implemented yet... + } +} diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index a24ef753b..42ee0a0cd 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -53,6 +53,7 @@ + diff --git a/ICSharpCode.Decompiler/Tests/YieldReturn.cs b/ICSharpCode.Decompiler/Tests/YieldReturn.cs new file mode 100644 index 000000000..de9bd1efb --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/YieldReturn.cs @@ -0,0 +1,103 @@ +// 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; + +public static class YieldReturn +{ + public static IEnumerable SimpleYieldReturn() + { + yield return "A"; + yield return "B"; + yield return "C"; + } + + public static IEnumerable YieldReturnInLoop() + { + for (int i = 0; i < 100; i++) { + yield return i; + } + } + + public static IEnumerable YieldReturnWithTryFinally() + { + yield return 0; + try { + yield return 1; + } finally { + Console.WriteLine("Finally!"); + } + yield return 2; + } + + + public static IEnumerable YieldReturnWithNestedTryFinally(bool breakInMiddle) + { + Console.WriteLine("Start of method - 1"); + yield return "Start of method"; + Console.WriteLine("Start of method - 2"); + try { + Console.WriteLine("Within outer try - 1"); + yield return "Within outer try"; + Console.WriteLine("Within outer try - 2"); + try { + Console.WriteLine("Within inner try - 1"); + yield return "Within inner try"; + Console.WriteLine("Within inner try - 2"); + if (breakInMiddle) + yield break; + Console.WriteLine("End of inner try - 1"); + yield return "End of inner try"; + Console.WriteLine("End of inner try - 2"); + } finally { + Console.WriteLine("Inner Finally"); + } + Console.WriteLine("End of outer try - 1"); + yield return "End of outer try"; + Console.WriteLine("End of outer try - 2"); + } finally { + Console.WriteLine("Outer Finally"); + } + Console.WriteLine("End of method - 1"); + yield return "End of method"; + Console.WriteLine("End of method - 2"); + } + + public static IEnumerable YieldReturnWithTwoNonNestedFinallyBlocks(IEnumerable input) + { + // outer try-finally block + foreach (string line in input) { + // nested try-finally block + try { + yield return line; + } finally { + Console.WriteLine("Processed " + line); + } + } + yield return "A"; + yield return "B"; + yield return "C"; + yield return "D"; + yield return "E"; + yield return "F"; + // outer try-finally block + foreach (string line in input) + yield return line.ToUpper(); + } + + public static IEnumerable> YieldReturnWithAnonymousMethods1(IEnumerable input) + { + foreach (string line in input) { + yield return () => line; + } + } + + public static IEnumerable> YieldReturnWithAnonymousMethods2(IEnumerable input) + { + foreach (string line in input) { + string copy = line; + yield return () => copy; + } + } +} diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index c01caaf1c..60fc12388 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -376,7 +376,8 @@ namespace ICSharpCode.ILSpy return new AstBuilder( new DecompilerContext { CancellationToken = options.CancellationToken, - CurrentType = currentType + CurrentType = currentType, + Settings = options.DecompilerSettings }); } diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index 8dc953c6d..bcd59af6b 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -18,6 +18,7 @@ using System; using System.Threading; +using ICSharpCode.Decompiler; namespace ICSharpCode.ILSpy { @@ -45,5 +46,15 @@ namespace ICSharpCode.ILSpy /// to allow for cooperative cancellation of the decompilation task. /// public CancellationToken CancellationToken { get; set; } + + /// + /// Gets the settings for the decompiler. + /// + public DecompilerSettings DecompilerSettings { get; set; } + + public DecompilationOptions() + { + this.DecompilerSettings = DecompilerSettingsPanel.LoadDecompilerSettings(ILSpySettings.Load()); + } } } diff --git a/ILSpy/DecompilerSettingsPanel.xaml b/ILSpy/DecompilerSettingsPanel.xaml new file mode 100644 index 000000000..00732070c --- /dev/null +++ b/ILSpy/DecompilerSettingsPanel.xaml @@ -0,0 +1,9 @@ + + + Decompile anonymous methods/lambdas + Decompile enumerators (yield return) + + \ No newline at end of file diff --git a/ILSpy/DecompilerSettingsPanel.xaml.cs b/ILSpy/DecompilerSettingsPanel.xaml.cs new file mode 100644 index 000000000..694ad1040 --- /dev/null +++ b/ILSpy/DecompilerSettingsPanel.xaml.cs @@ -0,0 +1,57 @@ +// 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.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Xml.Linq; +using ICSharpCode.Decompiler; + +namespace ICSharpCode.ILSpy +{ + /// + /// Interaction logic for DecompilerSettingsPanel.xaml + /// + [ExportOptionPage("Decompiler")] + partial class DecompilerSettingsPanel : UserControl, IOptionPage + { + public DecompilerSettingsPanel() + { + InitializeComponent(); + } + + public void Load(ILSpySettings settings) + { + this.DataContext = LoadDecompilerSettings(settings); + } + + public static DecompilerSettings LoadDecompilerSettings(ILSpySettings settings) + { + XElement e = settings["DecompilerSettings"]; + DecompilerSettings s = new DecompilerSettings(); + s.AnonymousMethods = (bool?)e.Attribute("anonymousMethods") ?? s.AnonymousMethods; + s.YieldReturn = (bool?)e.Attribute("yieldReturn") ?? s.YieldReturn; + return s; + } + + public void Save(XElement root) + { + DecompilerSettings s = (DecompilerSettings)this.DataContext; + XElement section = new XElement("DecompilerSettings"); + section.SetAttributeValue("anonymousMethods", s.AnonymousMethods); + section.SetAttributeValue("yieldReturn", s.YieldReturn); + + XElement existingElement = root.Element("DecompilerSettings"); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + root.Add(section); + } + } +} \ No newline at end of file diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 41598a0a1..b094e107b 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -89,6 +89,10 @@ + + DecompilerSettingsPanel.xaml + Code + @@ -111,6 +115,10 @@ OpenFromGacDialog.xaml Code + + OptionsDialog.xaml + Code + README.txt @@ -168,8 +176,10 @@ SearchBox.cs + + DecompilerTextView.cs diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 5056a19a9..90867ade9 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -429,6 +429,11 @@ namespace ICSharpCode.ILSpy decompilerTextView.Decompile(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions()); } + public void RefreshDecompiledView() + { + TreeView_SelectionChanged(null, null); + } + public DecompilerTextView TextView { get { return decompilerTextView; } } diff --git a/ILSpy/OptionsDialog.xaml b/ILSpy/OptionsDialog.xaml new file mode 100644 index 000000000..c5a9b8fe7 --- /dev/null +++ b/ILSpy/OptionsDialog.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ILSpy/OptionsDialog.xaml.cs b/ILSpy/OptionsDialog.xaml.cs new file mode 100644 index 000000000..dc87b88bf --- /dev/null +++ b/ILSpy/OptionsDialog.xaml.cs @@ -0,0 +1,93 @@ +// 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.ComponentModel.Composition; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Xml.Linq; + +namespace ICSharpCode.ILSpy +{ + /// + /// Interaction logic for OptionsDialog.xaml + /// + public partial class OptionsDialog : Window + { + [ImportMany("OptionPages", typeof(UIElement), RequiredCreationPolicy = CreationPolicy.NonShared)] + Lazy[] optionPages = null; + + public OptionsDialog() + { + InitializeComponent(); + App.CompositionContainer.ComposeParts(this); + ILSpySettings settings = ILSpySettings.Load(); + foreach (var optionPage in optionPages) { + TabItem tabItem = new TabItem(); + tabItem.Header = optionPage.Metadata.Title; + tabItem.Content = optionPage.Value; + tabControl.Items.Add(tabItem); + + IOptionPage page = optionPage.Value as IOptionPage; + if (page != null) + page.Load(settings); + } + } + + void OKButton_Click(object sender, RoutedEventArgs e) + { + ILSpySettings.Update( + delegate (XElement root) { + foreach (var optionPage in optionPages) { + IOptionPage page = optionPage.Value as IOptionPage; + if (page != null) + page.Save(root); + } + }); + this.DialogResult = true; + Close(); + } + } + + public interface IOptionsMetadata + { + string Title { get; } + } + + public interface IOptionPage + { + void Load(ILSpySettings settings); + void Save(XElement root); + } + + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] + public class ExportOptionPageAttribute : ExportAttribute + { + public ExportOptionPageAttribute(string title) + : base("OptionPages", typeof(UIElement)) + { + this.Title = title; + } + + public string Title { get; private set; } + } + + [ExportMainMenuCommand(Menu = "_View", Header = "_Options", MenuCategory = "Options", MenuOrder = 999)] + sealed class ShowOptionsCommand : SimpleCommand + { + public override void Execute(object parameter) + { + OptionsDialog dlg = new OptionsDialog(); + dlg.Owner = MainWindow.Instance; + if (dlg.ShowDialog() == true) + MainWindow.Instance.RefreshDecompiledView(); + } + } +} \ No newline at end of file