├── .gitignore ├── Directory.Build.props ├── ExpressionDebugger.Console ├── ExpressionDebugger.Console.csproj └── Program.cs ├── ExpressionDebugger.Tests ├── DebugInfoInjectorTest.cs └── ExpressionDebugger.Tests.csproj ├── ExpressionDebugger.sln ├── ExpressionDebugger ├── ExpressionCompilationOptions.cs ├── ExpressionCompiler.cs ├── ExpressionDebugger.csproj ├── ExpressionDebugger.snk └── ExpressionDebuggerExtensions.cs ├── ExpressionTranslator ├── ExpressionDefinitions.cs ├── ExpressionTranslator.cs ├── ExpressionTranslator.csproj ├── ExpressionTranslator.snk ├── ExpressionTranslatorExtensions.cs ├── Extensions.cs ├── PropertyDefinitions.cs └── TypeDefinitions.cs ├── LICENSE ├── Pack.bat ├── Publish.bat ├── README.md └── icon.png /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | 8 | chaowlert;andrerav 9 | Copyright (c) $([System.DateTime]::Now.ToString(`yyyy`)) Chaowlert Chaisrichalermpol, Andreas Ravnestad 10 | false 11 | https://github.com/MapsterMapper/ExpressionDebugger 12 | https://github.com/MapsterMapper/ExpressionDebugger 13 | ./icon.png 14 | https://cloud.githubusercontent.com/assets/5763993/26522656/41e28a6e-432f-11e7-9cae-7856f927d1a1.png 15 | MIT 16 | expression;linq;debug 17 | 10 18 | 19 | -------------------------------------------------------------------------------- /ExpressionDebugger.Console/ExpressionDebugger.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ExpressionDebugger.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace ExpressionDebugger.Console 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | var p1 = Expression.Parameter(typeof(int)); 11 | var p2 = Expression.Parameter(typeof(int)); 12 | var body = Expression.Add(p1, Expression.Block( 13 | new Expression[] { 14 | Expression.Call(typeof(System.Console).GetMethod("WriteLine", new [] { typeof(int) }), p2), 15 | p2, 16 | })); 17 | var lambda = Expression.Lambda>(body, p1, p2); 18 | var fun = lambda.CompileWithDebugInfo(); 19 | var result = fun(1, 2); 20 | System.Console.WriteLine(result); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ExpressionDebugger.Tests/DebugInfoInjectorTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace ExpressionDebugger.Tests 9 | { 10 | [TestClass] 11 | public class DebugInfoInjectorTest 12 | { 13 | [TestMethod] 14 | public void TestBinary() 15 | { 16 | Expression> fn = (a, b) => a + b; 17 | var str = fn.ToScript(); 18 | Assert.AreEqual(@" 19 | public int Main(int a, int b) 20 | { 21 | return a + b; 22 | }".Trim() 23 | , str); 24 | } 25 | 26 | [TestMethod] 27 | public void TestBinary_PowerAssign() 28 | { 29 | var exp = Expression.PowerAssign(Expression.Variable(typeof(double), "d"), Expression.Constant(2d)); 30 | var str = exp.ToScript(); 31 | Assert.AreEqual("d = Math.Pow(d, 2d)", str); 32 | } 33 | 34 | [TestMethod] 35 | public void TestBinary_ArrayIndex() 36 | { 37 | Expression> fn = a => a[0]; 38 | var str = fn.ToScript(); 39 | Assert.AreEqual(@" 40 | public int Main(int[] a) 41 | { 42 | return a[0]; 43 | }".Trim() 44 | , str); 45 | } 46 | 47 | [TestMethod] 48 | public void TestBlock() 49 | { 50 | var p1 = Expression.Variable(typeof(int)); 51 | var block = Expression.Block(new[] { p1 }, Expression.Add(p1, p1)); 52 | var str = block.ToScript(); 53 | Assert.AreEqual(@" 54 | int p1; 55 | 56 | p1 + p1;".Trim(), str); 57 | } 58 | 59 | [TestMethod] 60 | public void Test_Conditional() 61 | { 62 | Expression> fn = a => a < 0 ? a - 1 : a + 1; 63 | var str = fn.ToScript(); 64 | Assert.AreEqual(@" 65 | public int Main(int a) 66 | { 67 | return a < 0 ? a - 1 : a + 1; 68 | }".Trim() 69 | , str); 70 | } 71 | 72 | [TestMethod] 73 | public void TestConditional_Block() 74 | { 75 | var exp = Expression.Condition( 76 | Expression.Equal(Expression.Constant(1), Expression.Constant(2)), 77 | Expression.Constant(4), 78 | Expression.Constant(3), 79 | typeof(void)); 80 | var str = exp.ToScript(); 81 | Assert.AreEqual(@" 82 | if (1 == 2) 83 | { 84 | 4; 85 | } 86 | else 87 | { 88 | 3; 89 | }".Trim() 90 | , str); 91 | } 92 | 93 | [TestMethod] 94 | public void TestConditional_Block_Chain() 95 | { 96 | var exp = Expression.Condition( 97 | Expression.Equal(Expression.Constant(1), Expression.Constant(2)), 98 | Expression.Constant(4), 99 | Expression.Condition( 100 | Expression.Equal(Expression.Constant(5), Expression.Constant(6)), 101 | Expression.Constant(3), 102 | Expression.Constant(2), 103 | typeof(void)), 104 | typeof(void)); 105 | var str = exp.ToScript(); 106 | Assert.AreEqual(@" 107 | if (1 == 2) 108 | { 109 | 4; 110 | } 111 | else if (5 == 6) 112 | { 113 | 3; 114 | } 115 | else 116 | { 117 | 2; 118 | }".Trim() 119 | , str); 120 | } 121 | 122 | [TestMethod] 123 | public void TestConstants() 124 | { 125 | Expression> fn = s => s == "x" || s == @"\" || s == null || s.IsNormalized() == false || s.GetType() == typeof(string) ? 'x' : s[0]; 126 | var str = fn.ToScript(); 127 | Assert.AreEqual(@" 128 | public char Main(string s) 129 | { 130 | return s == ""x"" || s == @""\"" || s == null || s.IsNormalized() == false || s.GetType() == typeof(string) ? 'x' : s[0]; 131 | }".Trim() 132 | , str); 133 | 134 | Expression> fn2 = () => 1f.ToString() + 2m.ToString() + ((byte)1).ToString() + DayOfWeek.Friday.ToString() + default(DateTime).ToString(); 135 | var str2 = fn2.ToScript(); 136 | Assert.AreEqual(@" 137 | public string Main() 138 | { 139 | return 1f.ToString() + 2m.ToString() + ((byte)1).ToString() + DayOfWeek.Friday.ToString() + default(DateTime).ToString(); 140 | }".Trim() 141 | , str2); 142 | } 143 | 144 | [TestMethod] 145 | public void TestConstant_DateTime() 146 | { 147 | var now = DateTime.Now; 148 | var expr = Expression.Constant(now); 149 | var script = expr.ToScript(); 150 | Assert.AreEqual(@" 151 | private DateTime DateTime1; 152 | DateTime1".Trim(), script); 153 | } 154 | 155 | // [TestMethod] 156 | // public void TestDynamic() 157 | // { 158 | // var dynType = typeof(ExpandoObject); 159 | // var p1 = Expression.Variable(dynType); 160 | // var line1 = Expression.Dynamic(Binder.Convert(CSharpBinderFlags.None, typeof(Poco), dynType), typeof(object), p1); 161 | // var line2 = Expression.Dynamic(Binder.GetMember(CSharpBinderFlags.None, "Blah", dynType, 162 | // new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }), typeof(object), p1); 163 | // var line3 = Expression.Dynamic(Binder.SetMember(CSharpBinderFlags.None, "Blah", dynType, 164 | // new[] 165 | // { 166 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 167 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 168 | // }), typeof(object), p1, Expression.Constant(0)); 169 | // var line4 = Expression.Dynamic(Binder.GetIndex(CSharpBinderFlags.None, dynType, 170 | // new[] 171 | // { 172 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 173 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 174 | // }), typeof(object), p1, Expression.Constant(1)); 175 | // var line5 = Expression.Dynamic(Binder.SetIndex(CSharpBinderFlags.None, dynType, 176 | // new[] 177 | // { 178 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 179 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 180 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 181 | // }), typeof(object), p1, Expression.Constant(1), Expression.Constant(0)); 182 | // var line6 = Expression.Dynamic(Binder.InvokeMember(CSharpBinderFlags.None, "Blah", null, dynType, 183 | // new[] 184 | // { 185 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 186 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 187 | // }), typeof(object), p1, Expression.Constant(2)); 188 | // var line7 = Expression.Dynamic(Binder.Invoke(CSharpBinderFlags.None, dynType, 189 | // new[] 190 | // { 191 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 192 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 193 | // }), typeof(object), p1, Expression.Constant(2)); 194 | // var line8 = Expression.Dynamic(Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.Negate, dynType, 195 | // new[] 196 | // { 197 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 198 | // }), typeof(object), p1); 199 | // var line9 = Expression.Dynamic(Binder.BinaryOperation(CSharpBinderFlags.None, ExpressionType.Add, dynType, 200 | // new[] 201 | // { 202 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 203 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 204 | // }), typeof(object), p1, Expression.Constant(3)); 205 | // var expr = Expression.Block(new[] { p1 }, line1, line2, line3, line4, line5, line6, line7, line8, line9); 206 | // var str = expr.ToScript(); 207 | // Assert.AreEqual(@" 208 | //dynamic p1; 209 | 210 | //(Poco)p1; 211 | //p1.Blah; 212 | //p1.Blah = 0; 213 | //p1[1]; 214 | //p1[1] = 0; 215 | //p1.Blah(2); 216 | //p1(2); 217 | //-p1; 218 | //p1 + 3;" 219 | // , str); 220 | // } 221 | 222 | [TestMethod] 223 | public void TestDefault() 224 | { 225 | var exp = Expression.Default(typeof(int)); 226 | var str = exp.ToScript(); 227 | Assert.AreEqual("default(int)", str); 228 | } 229 | 230 | [TestMethod] 231 | public void TestGroup() 232 | { 233 | Expression> fn = x => -(-x) + 1 + x - (1 - x); 234 | var str = fn.ToScript(); 235 | Assert.AreEqual(@" 236 | public int Main(int x) 237 | { 238 | return -(-x) + 1 + x - (1 - x); 239 | }".Trim() 240 | , str); 241 | } 242 | 243 | [TestMethod] 244 | public void TestGroup_MultiLine() 245 | { 246 | var p = Expression.Variable(typeof(int), "p"); 247 | var exp = Expression.Add( 248 | p, 249 | Expression.Block( 250 | new Expression[] { 251 | Expression.Call(typeof(Console).GetMethod(nameof(Console.WriteLine), new [] { typeof(int) }), p), 252 | p, 253 | } 254 | )); 255 | var str = exp.ToScript(); 256 | Assert.AreEqual(@"p + (new Func(() => { 257 | Console.WriteLine(p); 258 | return p; 259 | }))()" 260 | , str); 261 | } 262 | 263 | [TestMethod] 264 | public void TestIndex() 265 | { 266 | var p1 = Expression.Parameter(typeof(int[])); 267 | var expr = Expression.MakeIndex(p1, null, new[] { Expression.Constant(1) }); 268 | var str = expr.ToScript(); 269 | Assert.AreEqual("p1[1]", str); 270 | } 271 | 272 | [TestMethod] 273 | public void TestLambda() 274 | { 275 | var p1 = Expression.Parameter(typeof(int)); 276 | var func1 = Expression.Lambda( 277 | Expression.Increment(p1), 278 | p1); 279 | var expr = Expression.Block( 280 | Expression.Add( 281 | Expression.Invoke(func1, Expression.Constant(1)), 282 | Expression.Invoke(func1, Expression.Constant(1)))); 283 | var str = expr.ToScript(); 284 | Assert.AreEqual(@" 285 | func1(1) + func1(1); 286 | 287 | private int func1(int p1) 288 | { 289 | return p1 + 1; 290 | }".Trim() 291 | , str); 292 | } 293 | 294 | [TestMethod] 295 | public void TestLambda_Inline() 296 | { 297 | Expression, IQueryable>> fn = q => q.Where(it => it > 0); 298 | var str = fn.ToScript(); 299 | Assert.AreEqual(@" 300 | public IQueryable Main(IQueryable q) 301 | { 302 | return q.Where(it => it > 0); 303 | }".Trim() 304 | , str); 305 | } 306 | 307 | [TestMethod] 308 | public void TestListInit() 309 | { 310 | Expression>> list = () => new List() { 1, 2, 3 }; 311 | var str = list.ToScript(); 312 | Assert.AreEqual(@" 313 | public List Main() 314 | { 315 | return new List() {1, 2, 3}; 316 | }".Trim() 317 | , str); 318 | } 319 | 320 | [TestMethod] 321 | public void TestListInit_Dictionary() 322 | { 323 | Expression>> list = () => new Dictionary() 324 | { 325 | {1, 2}, 326 | {3, 4} 327 | }; 328 | var str = list.ToScript(); 329 | Assert.AreEqual(@" 330 | public Dictionary Main() 331 | { 332 | return new Dictionary() {{1, 2}, {3, 4}}; 333 | }".Trim() 334 | , str); 335 | } 336 | 337 | [TestMethod] 338 | public void TestLoop() 339 | { 340 | var @break = Expression.Label(); 341 | var @continue = Expression.Label(); 342 | var @return = Expression.Label(); 343 | var p1 = Expression.Parameter(typeof(int)); 344 | var expr = Expression.Loop( 345 | Expression.Condition( 346 | Expression.GreaterThanOrEqual(p1, Expression.Constant(1)), 347 | Expression.Condition( 348 | Expression.Equal(p1, Expression.Constant(1)), 349 | Expression.Return(@return, p1), 350 | Expression.Block( 351 | Expression.PostDecrementAssign(p1), 352 | Expression.Continue(@continue))), 353 | Expression.Break(@break)), 354 | @break, 355 | @continue); 356 | 357 | var str = expr.ToScript(); 358 | Assert.AreEqual(@" 359 | while (p1 >= 1) 360 | { 361 | if (p1 == 1) 362 | { 363 | return p1; 364 | } 365 | else 366 | { 367 | p1--; 368 | continue; 369 | } 370 | }".Trim() 371 | , str); 372 | } 373 | 374 | [TestMethod] 375 | public void TestLoop2() 376 | { 377 | var label = Expression.Label(); 378 | var expr = Expression.Block( 379 | Expression.Loop(Expression.Goto(label)), 380 | Expression.Label(label)); 381 | var str = expr.ToScript(); 382 | Assert.AreEqual(@" 383 | while (true) 384 | { 385 | goto label1; 386 | } 387 | label1:".Trim() 388 | , str); 389 | } 390 | 391 | [TestMethod] 392 | public void TestMemberAccess() 393 | { 394 | Expression> fn = () => DateTime.Now.Day; 395 | var str = fn.ToScript(); 396 | Assert.AreEqual(@" 397 | public int Main() 398 | { 399 | return DateTime.Now.Day; 400 | }".Trim() 401 | , str); 402 | } 403 | 404 | private class Poco 405 | { 406 | public string Name { get; set; } 407 | public Poco Parent { get; } = new Poco(); 408 | public List Children { get; } = new List(); 409 | } 410 | 411 | [TestMethod] 412 | public void TestMemberInit() 413 | { 414 | Expression> fn = () => new Poco() 415 | { 416 | Name = "1", 417 | Parent = { Name = "2" }, 418 | Children = { new Poco(), new Poco() } 419 | }; 420 | var str = fn.ToScript(); 421 | Assert.AreEqual(@" 422 | public DebugInfoInjectorTest.Poco Main() 423 | { 424 | return new DebugInfoInjectorTest.Poco() 425 | { 426 | Name = ""1"", 427 | Parent = {Name = ""2""}, 428 | Children = {new DebugInfoInjectorTest.Poco(), new DebugInfoInjectorTest.Poco()} 429 | }; 430 | }".Trim() 431 | , str); 432 | } 433 | 434 | [TestMethod] 435 | public void TestMethodCall_Default() 436 | { 437 | Expression, int>> fn = dict => dict[0]; 438 | var str = fn.ToScript(); 439 | Assert.AreEqual(@" 440 | public int Main(Dictionary dict) 441 | { 442 | return dict[0]; 443 | }".Trim() 444 | , str); 445 | } 446 | 447 | [TestMethod] 448 | public void TestMethodCall() 449 | { 450 | Expression, string>> fn = list => list.ToString(); 451 | var str = fn.ToScript(); 452 | Assert.AreEqual(@" 453 | public string Main(List list) 454 | { 455 | return list.ToString(); 456 | }".Trim() 457 | , str); 458 | } 459 | 460 | [TestMethod] 461 | public void TestMethodCall_Static() 462 | { 463 | Expression> fn = (a, b) => Math.Max(a, b); 464 | var str = fn.ToScript(); 465 | Assert.AreEqual(@" 466 | public int Main(int a, int b) 467 | { 468 | return Math.Max(a, b); 469 | }".Trim() 470 | , str); 471 | } 472 | 473 | [TestMethod] 474 | public void TestNewArray() 475 | { 476 | Expression> fn = i => new[] { i }; 477 | var str = fn.ToScript(); 478 | Assert.AreEqual(@" 479 | public int[] Main(int i) 480 | { 481 | return new int[] {i}; 482 | }".Trim() 483 | , str); 484 | } 485 | 486 | [TestMethod] 487 | public void TestNewArray_Bound() 488 | { 489 | Expression> fn = i => new int[i]; 490 | var str = fn.ToScript(); 491 | Assert.AreEqual(@" 492 | public int[] Main(int i) 493 | { 494 | return new int[i]; 495 | }".Trim() 496 | , str); 497 | } 498 | 499 | [TestMethod] 500 | public void TestParameter_Reserved() 501 | { 502 | Expression> fn = @null => @null.Value; 503 | var str = fn.ToScript(); 504 | Assert.AreEqual(@" 505 | public int Main(int? @null) 506 | { 507 | return @null.Value; 508 | }".Trim() 509 | , str); 510 | } 511 | 512 | [TestMethod] 513 | public void TestSwitch() 514 | { 515 | var p1 = Expression.Parameter(typeof(int)); 516 | var expr = Expression.Switch( 517 | p1, 518 | Expression.Constant(0), 519 | Expression.SwitchCase(Expression.Constant(1), Expression.Constant(1))); 520 | var str = expr.ToScript(); 521 | Assert.AreEqual(@" 522 | switch (p1) 523 | { 524 | case 1: 525 | 1; 526 | break; 527 | default: 528 | 0; 529 | break; 530 | }".Trim() 531 | , str); 532 | } 533 | 534 | [TestMethod] 535 | public void TestTryCatch() 536 | { 537 | var p1 = Expression.Parameter(typeof(double)); 538 | var tryCatch = Expression.TryCatchFinally( 539 | Expression.Block( 540 | typeof(void), 541 | Expression.Assign( 542 | p1, 543 | Expression.Divide(Expression.Constant(1.0), Expression.Constant(0.0)))), 544 | Expression.Throw(Expression.New(typeof(NotSupportedException))), 545 | Expression.Catch( 546 | Expression.Parameter(typeof(DivideByZeroException)), 547 | Expression.Rethrow(), 548 | Expression.Constant(true))); 549 | var str = tryCatch.ToScript(); 550 | Assert.AreEqual(@" 551 | try 552 | { 553 | p1 = 1d / 0d; 554 | } 555 | catch (DivideByZeroException p2) when (true) 556 | { 557 | throw; 558 | } 559 | finally 560 | { 561 | throw new NotSupportedException(); 562 | }".Trim() 563 | , str); 564 | } 565 | 566 | [TestMethod] 567 | public void TestTryFault() 568 | { 569 | var expr = Expression.TryFault( 570 | Expression.Constant(1), 571 | Expression.Constant("blah")); 572 | var str = expr.ToScript(); 573 | Assert.AreEqual(@" 574 | bool fault1 = true; 575 | try 576 | { 577 | 1; 578 | fault1 = false; 579 | } 580 | finally 581 | { 582 | if (fault1) 583 | { 584 | ""blah""; 585 | } 586 | }".Trim() 587 | , str); 588 | } 589 | 590 | [TestMethod] 591 | public void TestTypeBinary() 592 | { 593 | Expression> fn = o => o is Array; 594 | var str = fn.ToScript(); 595 | Assert.AreEqual(@" 596 | public bool Main(object o) 597 | { 598 | return o is Array; 599 | }".Trim() 600 | , str); 601 | } 602 | 603 | [TestMethod] 604 | public void TestUnary_Convert() 605 | { 606 | Expression> fn = d => (int)d; 607 | var str = fn.ToScript(); 608 | Assert.AreEqual(@" 609 | public int Main(double d) 610 | { 611 | return (int)d; 612 | }".Trim() 613 | , str); 614 | } 615 | 616 | [TestMethod] 617 | public void TestUnary_ArrayLength() 618 | { 619 | Expression> fn = a => a.Length; 620 | var str = fn.ToScript(); 621 | Assert.AreEqual(@" 622 | public int Main(int[] a) 623 | { 624 | return a.Length; 625 | }".Trim() 626 | , str); 627 | } 628 | 629 | [TestMethod] 630 | public void TestUnary_As() 631 | { 632 | Expression> fn = expr => expr as UnaryExpression; 633 | var str = fn.ToScript(); 634 | Assert.AreEqual(@" 635 | public Expression Main(Expression expr) 636 | { 637 | return expr as UnaryExpression; 638 | }".Trim() 639 | , str); 640 | } 641 | 642 | internal static int GetInternal() => 1; 643 | 644 | [TestMethod] 645 | public void TestToString() 646 | { 647 | var call = Expression.Call( 648 | typeof(DebugInfoInjectorTest).GetMethod(nameof(GetInternal), 649 | BindingFlags.Static | BindingFlags.NonPublic) 650 | ); 651 | var exp = Expression.Lambda>(call); 652 | var str = exp.ToScript(new ExpressionDefinitions 653 | { 654 | IsStatic = true, 655 | MethodName = "Main", 656 | Namespace = "ExpressionDebugger.Tests", 657 | TypeName = "MockClass" 658 | }); 659 | Assert.AreEqual(@" 660 | using System; 661 | 662 | namespace ExpressionDebugger.Tests 663 | { 664 | public static partial class MockClass 665 | { 666 | private static Func GetInternal1; 667 | 668 | public static int Main() 669 | { 670 | return GetInternal1.Invoke(); 671 | } 672 | } 673 | }".Trim() 674 | , str); 675 | } 676 | 677 | [TestMethod] 678 | public void TestExpression() 679 | { 680 | Expression> lambda = data => new Data {Id = data.Id + "1", Records = data.Records.Select(it => it + 1)}; 681 | var str = lambda.ToScript(new ExpressionDefinitions {IsExpression = true}); 682 | Assert.AreEqual(@" 683 | public Expression> Main => data => new DebugInfoInjectorTest.Data() 684 | { 685 | Id = data.Id + ""1"", 686 | Records = data.Records.Select(it => it + 1) 687 | };".Trim(), str); 688 | } 689 | 690 | [TestMethod] 691 | public void TestExtensionMethod() 692 | { 693 | var p1 = Expression.Parameter(typeof(int)); 694 | var p2 = Expression.Parameter(typeof(int)); 695 | var lambda = Expression.Lambda>( 696 | Expression.Add(p1, p2), 697 | p1, p2); 698 | var translator = new ExpressionTranslator(new TypeDefinitions 699 | { 700 | IsStatic = true, 701 | Namespace = "ExpressionDebugger.Tests", 702 | TypeName = "MockClass" 703 | }); 704 | translator.VisitLambda(lambda, ExpressionTranslator.LambdaType.ExtensionMethod, "Add"); 705 | var str = translator.ToString(); 706 | Assert.AreEqual(@" 707 | namespace ExpressionDebugger.Tests 708 | { 709 | public static partial class MockClass 710 | { 711 | public static int Add(this int p1, int p2) 712 | { 713 | return p1 + p2; 714 | } 715 | } 716 | }".Trim(), str); 717 | } 718 | 719 | [TestMethod] 720 | public void TestProperties() 721 | { 722 | var translator = new ExpressionTranslator(new TypeDefinitions 723 | { 724 | IsStatic = false, 725 | Namespace = "ExpressionDebugger.Tests", 726 | TypeName = "MockClass", 727 | NullableContext = 2, 728 | }); 729 | translator.Properties.Add(new PropertyDefinitions 730 | { 731 | Name = "Prop1", 732 | Type = typeof(string), 733 | NullableContext = 0, 734 | }); 735 | translator.Properties.Add(new PropertyDefinitions 736 | { 737 | Name = "Prop2", 738 | Type = typeof(List>), 739 | IsReadOnly = true, 740 | Nullable = new byte[] { 1, 2, 1, 2 } 741 | }); 742 | translator.Properties.Add(new PropertyDefinitions 743 | { 744 | Name = "Prop3", 745 | Type = typeof(string), 746 | IsInitOnly = true, 747 | NullableContext = 0, 748 | }); 749 | var str = translator.ToString(); 750 | Assert.AreEqual(@" 751 | using System.Collections.Generic; 752 | 753 | namespace ExpressionDebugger.Tests 754 | { 755 | public partial class MockClass 756 | { 757 | public string Prop1 { get; set; } 758 | public List?> Prop2 { get; } 759 | public string Prop3 { get; init; } 760 | 761 | public MockClass(List?> prop2) 762 | { 763 | this.Prop2 = prop2; 764 | } 765 | } 766 | }".Trim(), str); 767 | } 768 | 769 | 770 | [TestMethod] 771 | public void TestRecordType() 772 | { 773 | var translator = new ExpressionTranslator(new TypeDefinitions 774 | { 775 | IsStatic = false, 776 | Namespace = "ExpressionDebugger.Tests", 777 | TypeName = "MockClass", 778 | IsRecordType = true, 779 | }); 780 | translator.Properties.Add(new PropertyDefinitions 781 | { 782 | Name = "Prop1", 783 | Type = typeof(string) 784 | }); 785 | translator.Properties.Add(new PropertyDefinitions 786 | { 787 | Name = "Prop2", 788 | Type = typeof(string), 789 | IsReadOnly = true 790 | }); 791 | translator.Properties.Add(new PropertyDefinitions 792 | { 793 | Name = "Prop3", 794 | Type = typeof(string), 795 | IsInitOnly = true 796 | }); 797 | var str = translator.ToString(); 798 | Assert.AreEqual(@" 799 | namespace ExpressionDebugger.Tests 800 | { 801 | public partial record MockClass(string Prop2) 802 | { 803 | public string Prop1 { get; set; } 804 | public string Prop3 { get; init; } 805 | } 806 | }".Trim(), str); 807 | } 808 | 809 | public class Data 810 | { 811 | public string Id { get; set; } 812 | public IEnumerable Records { get; set; } 813 | } 814 | } 815 | } 816 | -------------------------------------------------------------------------------- /ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ExpressionDebugger.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.4 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger", "ExpressionDebugger\ExpressionDebugger.csproj", "{A9B56FC4-B2BC-4A32-B1C0-B926F6259666}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger.Tests", "ExpressionDebugger.Tests\ExpressionDebugger.Tests.csproj", "{024FB14F-7B40-42E4-8B80-1D348914124C}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger.Console", "ExpressionDebugger.Console\ExpressionDebugger.Console.csproj", "{71E2EC60-6780-4AB8-9773-91B4939560FB}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionTranslator", "ExpressionTranslator\ExpressionTranslator.csproj", "{9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {024FB14F-7B40-42E4-8B80-1D348914124C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {024FB14F-7B40-42E4-8B80-1D348914124C}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {024FB14F-7B40-42E4-8B80-1D348914124C}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {024FB14F-7B40-42E4-8B80-1D348914124C}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {71E2EC60-6780-4AB8-9773-91B4939560FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {71E2EC60-6780-4AB8-9773-91B4939560FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {71E2EC60-6780-4AB8-9773-91B4939560FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {71E2EC60-6780-4AB8-9773-91B4939560FB}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /ExpressionDebugger/ExpressionCompilationOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | 4 | namespace ExpressionDebugger 5 | { 6 | public class ExpressionCompilationOptions 7 | { 8 | public ExpressionDefinitions? DefaultDefinitions { get; set; } 9 | public IEnumerable? References { get; set; } 10 | public bool EmitFile { get; set; } 11 | public string? RootPath { get; set; } 12 | public bool? IsRelease { get; set; } 13 | public bool ThrowOnFailedCompilation { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ExpressionDebugger/ExpressionCompiler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.Emit; 4 | using Microsoft.CodeAnalysis.Text; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Linq.Expressions; 11 | using System.Reflection; 12 | using System.Text; 13 | 14 | namespace ExpressionDebugger 15 | { 16 | public class ExpressionCompiler 17 | { 18 | public List Translators { get; } = new List(); 19 | 20 | private readonly ExpressionCompilationOptions? _options; 21 | public ExpressionCompiler(ExpressionCompilationOptions? options = null) 22 | { 23 | _options = options; 24 | } 25 | 26 | private readonly List _codes = new List(); 27 | public void AddFile(string code, string filename) 28 | { 29 | var buffer = Encoding.UTF8.GetBytes(code); 30 | 31 | var path = filename; 32 | if (_options?.EmitFile == true) 33 | { 34 | var root = _options?.RootPath 35 | ?? Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "GeneratedSources"); 36 | Directory.CreateDirectory(root); 37 | path = Path.Combine(root, filename); 38 | using var fs = new FileStream(path, FileMode.Create); 39 | fs.Write(buffer, 0, buffer.Length); 40 | } 41 | 42 | var sourceText = SourceText.From(buffer, buffer.Length, Encoding.UTF8, canBeEmbedded: true); 43 | 44 | var syntaxTree = CSharpSyntaxTree.ParseText( 45 | sourceText, 46 | new CSharpParseOptions(), 47 | path); 48 | 49 | var syntaxRootNode = syntaxTree.GetRoot() as CSharpSyntaxNode; 50 | var encoded = CSharpSyntaxTree.Create(syntaxRootNode, null, path, Encoding.UTF8); 51 | _codes.Add(encoded); 52 | } 53 | 54 | public void AddFile(LambdaExpression node, ExpressionDefinitions? definitions = null) 55 | { 56 | definitions ??= _options?.DefaultDefinitions ?? new ExpressionDefinitions {IsStatic = true}; 57 | definitions.TypeName ??= "Program"; 58 | 59 | var translator = ExpressionTranslator.Create(node, definitions); 60 | var script = translator.ToString(); 61 | Translators.Add(translator); 62 | 63 | this.AddFile(script, Path.ChangeExtension(Path.GetRandomFileName(), ".cs")); 64 | } 65 | 66 | public Assembly CreateAssembly() 67 | { 68 | var references = new HashSet(); 69 | references.UnionWith(from t in Translators 70 | from n in t.TypeNames 71 | select n.Key.Assembly); 72 | 73 | if (_options?.References != null) 74 | references.UnionWith(_options.References); 75 | references.Add(typeof(object).Assembly); 76 | 77 | #if NETSTANDARD2_0 78 | references.Add(Assembly.Load(new AssemblyName("netstandard"))); 79 | references.Add(Assembly.Load(new AssemblyName("System.Runtime"))); 80 | references.Add(Assembly.Load(new AssemblyName("System.Collections"))); 81 | #endif 82 | 83 | var assemblyName = Path.GetRandomFileName(); 84 | var symbolsName = Path.ChangeExtension(assemblyName, "pdb"); 85 | 86 | var metadataReferences = references.Select(it => MetadataReference.CreateFromFile(it.Location)); 87 | var isRelease = _options?.IsRelease ?? !Debugger.IsAttached; 88 | CSharpCompilation compilation = CSharpCompilation.Create( 89 | assemblyName, 90 | _codes, 91 | metadataReferences, 92 | new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, usings: new[] { "System" }) 93 | .WithOptimizationLevel(isRelease ? OptimizationLevel.Release : OptimizationLevel.Debug) 94 | .WithPlatform(Platform.AnyCpu) 95 | ); 96 | 97 | using var assemblyStream = new MemoryStream(); 98 | using var symbolsStream = new MemoryStream(); 99 | var emitOptions = new EmitOptions( 100 | debugInformationFormat: DebugInformationFormat.PortablePdb, 101 | pdbFilePath: symbolsName); 102 | 103 | var embeddedTexts = _codes.Select(it => EmbeddedText.FromSource(it.FilePath, it.GetText())); 104 | 105 | EmitResult result = compilation.Emit( 106 | peStream: assemblyStream, 107 | pdbStream: symbolsStream, 108 | embeddedTexts: embeddedTexts, 109 | options: emitOptions); 110 | 111 | if (!result.Success) 112 | { 113 | var errors = new List(); 114 | 115 | IEnumerable failures = result.Diagnostics.Where(diagnostic => 116 | diagnostic.IsWarningAsError || 117 | diagnostic.Severity == DiagnosticSeverity.Error); 118 | 119 | foreach (Diagnostic diagnostic in failures) 120 | errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}"); 121 | 122 | throw new InvalidOperationException(string.Join("\n", errors)); 123 | } 124 | 125 | assemblyStream.Seek(0, SeekOrigin.Begin); 126 | symbolsStream.Seek(0, SeekOrigin.Begin); 127 | 128 | #if NETSTANDARD2_0 129 | return System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, symbolsStream); 130 | #else 131 | return Assembly.Load(assemblyStream.ToArray(), symbolsStream.ToArray()); 132 | #endif 133 | } 134 | 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /ExpressionDebugger/ExpressionDebugger.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net461 5 | True 6 | Step into debugging from linq expressions 7 | True 8 | true 9 | ExpressionDebugger.snk 10 | 2.2.1 11 | enable 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ExpressionDebugger/ExpressionDebugger.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MapsterMapper/ExpressionDebugger/c4a153ea1bcfd9a02f234e2fb006423cb290c636/ExpressionDebugger/ExpressionDebugger.snk -------------------------------------------------------------------------------- /ExpressionDebugger/ExpressionDebuggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using ExpressionDebugger; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace System.Linq.Expressions 7 | { 8 | public static class ExpressionDebuggerExtensions 9 | { 10 | 11 | /// 12 | /// Compile with debugging info injected 13 | /// 14 | /// Type of lambda expression 15 | /// Lambda expression 16 | /// Compilation options 17 | /// Generated method 18 | public static T CompileWithDebugInfo(this Expression node, ExpressionCompilationOptions? options = null) 19 | { 20 | return (T)(object)CompileWithDebugInfo((LambdaExpression)node, options); 21 | } 22 | 23 | public static Delegate CompileWithDebugInfo(this LambdaExpression node, ExpressionCompilationOptions? options = null) 24 | { 25 | try 26 | { 27 | var compiler = new ExpressionCompiler(options); 28 | compiler.AddFile(node); 29 | var assembly = compiler.CreateAssembly(); 30 | 31 | var translator = compiler.Translators[0]; 32 | return translator.CreateDelegate(assembly); 33 | } 34 | catch (Exception ex) 35 | { 36 | if (options?.ThrowOnFailedCompilation == true) 37 | throw; 38 | Debug.Print(ex.ToString()); 39 | return node.Compile(); 40 | } 41 | } 42 | 43 | public static Delegate CreateDelegate(this ExpressionTranslator translator, Assembly assembly) 44 | { 45 | var definitions = translator.Definitions!; 46 | var typeName = definitions.Namespace == null 47 | ? definitions.TypeName 48 | : definitions.Namespace + "." + definitions.TypeName; 49 | var type = assembly.GetType(typeName); 50 | var main = translator.Methods.First(); 51 | var method = type.GetMethod(main.Key)!; 52 | var obj = definitions.IsStatic ? null : Activator.CreateInstance(type); 53 | var flag = definitions.IsStatic ? BindingFlags.Static : BindingFlags.Instance; 54 | foreach (var kvp in translator.Constants) 55 | { 56 | var field = type.GetField(kvp.Value, BindingFlags.NonPublic | flag)!; 57 | field.SetValue(obj, kvp.Key); 58 | } 59 | return method.CreateDelegate(main.Value, obj); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ExpressionTranslator/ExpressionDefinitions.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionDebugger 2 | { 3 | public class ExpressionDefinitions : TypeDefinitions 4 | { 5 | public string? MethodName { get; set; } 6 | public bool IsExpression { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ExpressionTranslator/ExpressionTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | #if !NETSTANDARD1_3 7 | using System.Dynamic; 8 | #endif 9 | using System.IO; 10 | using System.Linq; 11 | using System.Linq.Expressions; 12 | using System.Reflection; 13 | using System.Runtime.CompilerServices; 14 | 15 | namespace ExpressionDebugger 16 | { 17 | public class ExpressionTranslator : ExpressionVisitor 18 | { 19 | private const int Tabsize = 4; 20 | private StringWriter _writer; 21 | private int _indentLevel; 22 | private List? _appendWriters; 23 | 24 | private HashSet? _usings; 25 | private Dictionary? _defaults; 26 | 27 | private Dictionary? _constants; 28 | public Dictionary Constants => _constants ??= new Dictionary(); 29 | 30 | private Dictionary? _typeNames; 31 | public Dictionary TypeNames => _typeNames ??= new Dictionary(); 32 | 33 | private Dictionary? _methods; 34 | public Dictionary Methods => _methods ??= new Dictionary(); 35 | 36 | private List? _properties; 37 | public List Properties => _properties ??= new List(); 38 | 39 | public bool HasDynamic { get; private set; } 40 | public TypeDefinitions? Definitions { get; } 41 | 42 | public ExpressionTranslator(TypeDefinitions? definitions = null) 43 | { 44 | Definitions = definitions; 45 | _writer = new StringWriter(); 46 | ResetIndentLevel(); 47 | } 48 | 49 | private void ResetIndentLevel() 50 | { 51 | _indentLevel = 0; 52 | if (Definitions?.TypeName != null) 53 | { 54 | _indentLevel++; 55 | if (Definitions.Namespace != null) 56 | _indentLevel++; 57 | } 58 | } 59 | 60 | public static ExpressionTranslator Create(Expression node, ExpressionDefinitions? definitions = null) 61 | { 62 | var translator = new ExpressionTranslator(definitions); 63 | if (node.NodeType == ExpressionType.Lambda) 64 | translator.VisitLambda((LambdaExpression)node, 65 | definitions?.IsExpression == true ? LambdaType.PublicLambda : LambdaType.PublicMethod, 66 | definitions?.MethodName, 67 | definitions?.IsInternal ?? false); 68 | else 69 | translator.Visit(node); 70 | return translator; 71 | } 72 | 73 | private static int GetPrecedence(ExpressionType nodeType) 74 | { 75 | switch (nodeType) 76 | { 77 | // Assignment 78 | case ExpressionType.AddAssign: 79 | case ExpressionType.AddAssignChecked: 80 | case ExpressionType.AndAssign: 81 | case ExpressionType.Assign: 82 | case ExpressionType.DivideAssign: 83 | case ExpressionType.ExclusiveOrAssign: 84 | case ExpressionType.LeftShiftAssign: 85 | case ExpressionType.ModuloAssign: 86 | case ExpressionType.MultiplyAssign: 87 | case ExpressionType.MultiplyAssignChecked: 88 | case ExpressionType.OrAssign: 89 | //case ExpressionType.PowerAssign: 90 | case ExpressionType.Quote: 91 | case ExpressionType.RightShiftAssign: 92 | case ExpressionType.SubtractAssign: 93 | case ExpressionType.SubtractAssignChecked: 94 | case ExpressionType.Extension: 95 | return 1; 96 | 97 | // Conditional 98 | case ExpressionType.Coalesce: 99 | case ExpressionType.Conditional: 100 | return 2; 101 | 102 | // Conditional OR 103 | case ExpressionType.OrElse: 104 | return 3; 105 | 106 | // Conditional AND 107 | case ExpressionType.AndAlso: 108 | return 4; 109 | 110 | // Logical OR 111 | case ExpressionType.Or: 112 | return 5; 113 | 114 | // Logical XOR 115 | case ExpressionType.ExclusiveOr: 116 | return 6; 117 | 118 | // Logical AND 119 | case ExpressionType.And: 120 | return 7; 121 | 122 | // Equality 123 | case ExpressionType.Equal: 124 | case ExpressionType.NotEqual: 125 | return 8; 126 | 127 | // Relational and type testing 128 | case ExpressionType.GreaterThan: 129 | case ExpressionType.GreaterThanOrEqual: 130 | case ExpressionType.LessThan: 131 | case ExpressionType.LessThanOrEqual: 132 | case ExpressionType.TypeAs: 133 | case ExpressionType.TypeEqual: 134 | case ExpressionType.TypeIs: 135 | return 9; 136 | 137 | // Shift 138 | case ExpressionType.LeftShift: 139 | case ExpressionType.RightShift: 140 | return 10; 141 | 142 | // Additive 143 | case ExpressionType.Add: 144 | case ExpressionType.AddChecked: 145 | case ExpressionType.Decrement: 146 | case ExpressionType.Increment: 147 | case ExpressionType.Subtract: 148 | case ExpressionType.SubtractChecked: 149 | return 11; 150 | 151 | // Multiplicative 152 | case ExpressionType.Divide: 153 | case ExpressionType.Modulo: 154 | case ExpressionType.Multiply: 155 | case ExpressionType.MultiplyChecked: 156 | return 12; 157 | 158 | // Unary 159 | case ExpressionType.Convert: 160 | case ExpressionType.ConvertChecked: 161 | case ExpressionType.IsFalse: 162 | case ExpressionType.Negate: 163 | case ExpressionType.NegateChecked: 164 | case ExpressionType.Not: 165 | case ExpressionType.OnesComplement: 166 | case ExpressionType.PreDecrementAssign: 167 | case ExpressionType.PreIncrementAssign: 168 | case ExpressionType.UnaryPlus: 169 | return 13; 170 | 171 | //// Power 172 | //case ExpressionType.Power: 173 | // return 14; 174 | 175 | default: 176 | return 100; 177 | } 178 | } 179 | 180 | private static bool ShouldGroup(Expression? node, ExpressionType parentNodeType, bool isRightNode) 181 | { 182 | if (node == null) 183 | return false; 184 | 185 | var nodePrecedence = GetPrecedence(node.NodeType); 186 | var parentPrecedence = GetPrecedence(parentNodeType); 187 | 188 | if (nodePrecedence != parentPrecedence) 189 | return nodePrecedence < parentPrecedence; 190 | 191 | switch (parentNodeType) 192 | { 193 | //wrap to prevent confusion 194 | case ExpressionType.Conditional: 195 | case ExpressionType.Equal: 196 | case ExpressionType.NotEqual: 197 | case ExpressionType.Negate: 198 | case ExpressionType.NegateChecked: 199 | case ExpressionType.UnaryPlus: 200 | return true; 201 | 202 | //1-(1-1) != 1-1-1 203 | case ExpressionType.Divide: 204 | case ExpressionType.Modulo: 205 | case ExpressionType.LeftShift: 206 | //case ExpressionType.Power: 207 | case ExpressionType.RightShift: 208 | case ExpressionType.Subtract: 209 | case ExpressionType.SubtractChecked: 210 | return isRightNode; 211 | 212 | default: 213 | return false; 214 | } 215 | } 216 | 217 | private Expression Visit(string open, Expression node, params string[] end) 218 | { 219 | Write(open); 220 | var result = Visit(node)!; 221 | Write(end); 222 | return result; 223 | } 224 | 225 | private void Write(string text) 226 | { 227 | _writer.Write(text); 228 | } 229 | 230 | private void Write(params string[] texts) 231 | { 232 | foreach (var text in texts) 233 | { 234 | Write(text); 235 | } 236 | } 237 | 238 | private void WriteLine() 239 | { 240 | _writer.WriteLine(); 241 | 242 | var spaceCount = _indentLevel * Tabsize; 243 | _writer.Write(new string(' ', spaceCount)); 244 | } 245 | 246 | private Expression VisitNextLine(string open, Expression node, params string[] end) 247 | { 248 | WriteLine(); 249 | Write(open); 250 | var result = Visit(node)!; 251 | Write(end); 252 | return result; 253 | } 254 | 255 | private void WriteNextLine(string text) 256 | { 257 | WriteLine(); 258 | Write(text); 259 | } 260 | 261 | private void WriteNextLine(params string[] texts) 262 | { 263 | WriteLine(); 264 | foreach (var text in texts) 265 | { 266 | Write(text); 267 | } 268 | } 269 | 270 | private void Indent(bool inline = false) 271 | { 272 | if (!inline) 273 | WriteLine(); 274 | Write("{"); 275 | _indentLevel++; 276 | } 277 | 278 | private void Outdent() 279 | { 280 | _indentLevel--; 281 | WriteNextLine("}"); 282 | } 283 | 284 | private Expression VisitGroup(Expression node, ExpressionType parentNodeType, bool isRightNode = false) 285 | { 286 | Expression result; 287 | if (!IsInline(node)) 288 | { 289 | var func = typeof(Func<>).MakeGenericType(node.Type); 290 | Write("(new ", Translate(func), "(() => "); 291 | Indent(true); 292 | result = VisitMultiline(node, true); 293 | Outdent(); 294 | Write("))()"); 295 | } 296 | else if (ShouldGroup(node, parentNodeType, isRightNode)) 297 | { 298 | result = Visit("(", node, ")"); 299 | } 300 | else 301 | { 302 | result = Visit(node)!; 303 | } 304 | 305 | return result; 306 | } 307 | 308 | private static string Translate(ExpressionType nodeType) 309 | { 310 | switch (nodeType) 311 | { 312 | case ExpressionType.Add: return "+"; 313 | case ExpressionType.AddChecked: return "+"; 314 | case ExpressionType.AddAssign: return "+="; 315 | case ExpressionType.AddAssignChecked: return "+="; 316 | case ExpressionType.And: return "&"; 317 | case ExpressionType.AndAlso: return "&&"; 318 | case ExpressionType.AndAssign: return "&="; 319 | case ExpressionType.ArrayLength: return ".Length"; 320 | case ExpressionType.Assign: return "="; 321 | case ExpressionType.Coalesce: return "??"; 322 | case ExpressionType.Decrement: return " - 1"; 323 | case ExpressionType.Divide: return "/"; 324 | case ExpressionType.DivideAssign: return "/="; 325 | case ExpressionType.Equal: return "=="; 326 | case ExpressionType.ExclusiveOr: return "^"; 327 | case ExpressionType.ExclusiveOrAssign: return "^="; 328 | case ExpressionType.GreaterThan: return ">"; 329 | case ExpressionType.GreaterThanOrEqual: return ">="; 330 | case ExpressionType.Increment: return " + 1"; 331 | case ExpressionType.IsFalse: return "!"; 332 | case ExpressionType.IsTrue: return ""; 333 | case ExpressionType.Modulo: return "%"; 334 | case ExpressionType.ModuloAssign: return "%="; 335 | case ExpressionType.Multiply: return "*"; 336 | case ExpressionType.MultiplyAssign: return "*="; 337 | case ExpressionType.MultiplyAssignChecked: return "*="; 338 | case ExpressionType.MultiplyChecked: return "*"; 339 | case ExpressionType.Negate: return "-"; 340 | case ExpressionType.NegateChecked: return "-"; 341 | case ExpressionType.Not: return "!"; 342 | case ExpressionType.LeftShift: return "<<"; 343 | case ExpressionType.LeftShiftAssign: return "<<="; 344 | case ExpressionType.LessThan: return "<"; 345 | case ExpressionType.LessThanOrEqual: return "<="; 346 | case ExpressionType.NotEqual: return "!="; 347 | case ExpressionType.OnesComplement: return "~"; 348 | case ExpressionType.Or: return "|"; 349 | case ExpressionType.OrAssign: return "|="; 350 | case ExpressionType.OrElse: return "||"; 351 | case ExpressionType.PreDecrementAssign: return "--"; 352 | case ExpressionType.PreIncrementAssign: return "++"; 353 | case ExpressionType.PostDecrementAssign: return "--"; 354 | case ExpressionType.PostIncrementAssign: return "++"; 355 | //case ExpressionType.Power: return "**"; 356 | //case ExpressionType.PowerAssign: return "**="; 357 | case ExpressionType.RightShift: return ">>"; 358 | case ExpressionType.RightShiftAssign: return ">>="; 359 | case ExpressionType.Subtract: return "-"; 360 | case ExpressionType.SubtractChecked: return "-"; 361 | case ExpressionType.SubtractAssign: return "-="; 362 | case ExpressionType.SubtractAssignChecked: return "-="; 363 | case ExpressionType.Throw: return "throw"; 364 | case ExpressionType.TypeAs: return " as "; 365 | case ExpressionType.UnaryPlus: return "+"; 366 | case ExpressionType.Unbox: return ""; 367 | 368 | default: 369 | throw new InvalidOperationException(); 370 | } 371 | } 372 | 373 | protected override Expression VisitBinary(BinaryExpression node) 374 | { 375 | Expression left, right; 376 | if (node.NodeType == ExpressionType.ArrayIndex) 377 | { 378 | left = VisitGroup(node.Left, node.NodeType); 379 | right = Visit("[", node.Right, "]"); 380 | } 381 | else if (node.NodeType == ExpressionType.Power || node.NodeType == ExpressionType.PowerAssign) 382 | { 383 | if (node.NodeType == ExpressionType.PowerAssign) 384 | { 385 | VisitGroup(node.Left, node.NodeType); 386 | Write(" = "); 387 | } 388 | Write(Translate(typeof(Math)), ".Pow("); 389 | left = Visit(node.Left)!; 390 | Write(", "); 391 | right = Visit(node.Right)!; 392 | Write(")"); 393 | } 394 | else 395 | { 396 | left = VisitGroup(node.Left, node.NodeType); 397 | Write(" ", Translate(node.NodeType), " "); 398 | right = VisitGroup(node.Right, node.NodeType, true); 399 | } 400 | 401 | return node.Update(left, node.Conversion, right); 402 | } 403 | 404 | private byte? _nilCtx; 405 | private byte[]? _nil; 406 | private int _nilIndex; 407 | private string TranslateNullable(Type type, byte? nullableContext, byte[]? nullable) 408 | { 409 | try 410 | { 411 | _nilCtx = nullableContext; 412 | _nil = nullable; 413 | _nilIndex = 0; 414 | return Translate(type); 415 | } 416 | finally 417 | { 418 | _nilCtx = null; 419 | _nil = null; 420 | } 421 | } 422 | public string Translate(Type type) 423 | { 424 | var refNullable = !type.GetTypeInfo().IsValueType && 425 | (_nilIndex < _nil?.Length ? _nil[_nilIndex++] == 2 : _nilCtx == 2); 426 | var typeName = TranslateInner(type); 427 | return refNullable ? $"{typeName}?" : typeName; 428 | } 429 | private string TranslateInner(Type type) 430 | { 431 | if (type == typeof(bool)) 432 | return "bool"; 433 | if (type == typeof(byte)) 434 | return "byte"; 435 | if (type == typeof(char)) 436 | return "char"; 437 | if (type == typeof(decimal)) 438 | return "decimal"; 439 | if (type == typeof(double)) 440 | return "double"; 441 | if (type == typeof(float)) 442 | return "float"; 443 | if (type == typeof(int)) 444 | return "int"; 445 | if (type == typeof(long)) 446 | return "long"; 447 | if (type == typeof(object)) 448 | return "object"; 449 | if (type == typeof(sbyte)) 450 | return "sbyte"; 451 | if (type == typeof(short)) 452 | return "short"; 453 | if (type == typeof(string)) 454 | return "string"; 455 | if (type == typeof(uint)) 456 | return "uint"; 457 | if (type == typeof(ulong)) 458 | return "ulong"; 459 | if (type == typeof(ushort)) 460 | return "ushort"; 461 | if (type == typeof(void)) 462 | return "void"; 463 | #if !NETSTANDARD1_3 464 | if (typeof(IDynamicMetaObjectProvider).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) 465 | { 466 | HasDynamic = true; 467 | return "dynamic"; 468 | } 469 | #endif 470 | 471 | if (type.IsArray) 472 | { 473 | var rank = type.GetArrayRank(); 474 | return Translate(type.GetElementType()!) + "[" + new string(',', rank - 1) + "]"; 475 | } 476 | 477 | var underlyingType = Nullable.GetUnderlyingType(type); 478 | if (underlyingType != null) 479 | return Translate(underlyingType) + "?"; 480 | 481 | _usings ??= new HashSet(); 482 | 483 | string name; 484 | if (_nilCtx != null || _nil != null) 485 | { 486 | name = GetTypeName(type); 487 | if (Definitions?.PrintFullTypeName != true && !string.IsNullOrEmpty(type.Namespace)) 488 | _usings.Add(type.Namespace); 489 | } 490 | else if (!this.TypeNames.TryGetValue(type, out name)) 491 | { 492 | name = GetTypeName(type); 493 | 494 | if (Definitions?.PrintFullTypeName != true) 495 | { 496 | var count = this.TypeNames.Count(kvp => GetTypeName(kvp.Key) == name); 497 | if (count > 0) 498 | { 499 | // NOTE: type alias cannot solve all name conflicted case, user should use PrintFullTypeName 500 | // keep logic here for compatability 501 | if (!type.GetTypeInfo().IsGenericType) 502 | name += count + 1; 503 | else if (!string.IsNullOrEmpty(type.Namespace)) 504 | name = type.Namespace + '.' + name; 505 | } 506 | else if (!string.IsNullOrEmpty(type.Namespace)) 507 | _usings.Add(type.Namespace); 508 | } 509 | this.TypeNames.Add(type, name); 510 | } 511 | 512 | return name; 513 | } 514 | 515 | private static string GetVarName(string name) 516 | { 517 | var index = name.IndexOf('`'); 518 | if (index >= 0) 519 | name = name.Substring(0, index); 520 | return name; 521 | } 522 | 523 | private string GetTypeName(Type type) 524 | { 525 | var name = GetSingleTypeName(type); 526 | if (type.DeclaringType == null) 527 | return name; 528 | 529 | return TranslateInner(type.DeclaringType) + "." + name; 530 | } 531 | 532 | private string GetSingleTypeName(Type type) 533 | { 534 | var name = type.DeclaringType == null && Definitions?.PrintFullTypeName == true 535 | ? type.FullName! 536 | : type.Name; 537 | if (!type.GetTypeInfo().IsGenericType) 538 | { 539 | return name; 540 | } 541 | 542 | var index = name.IndexOf('`'); 543 | if (index >= 0) 544 | name = name.Substring(0, index); 545 | if (type.GetTypeInfo().IsGenericTypeDefinition) 546 | { 547 | var typeArgs = type.GetGenericArguments(); 548 | return name + "<" + new string(',', typeArgs.Length - 1) + ">"; 549 | } 550 | 551 | return name + "<" + string.Join(", ", type.GetGenericArguments().Select(Translate)) + ">"; 552 | } 553 | 554 | private static bool IsInline(Expression node) 555 | { 556 | switch (node.NodeType) 557 | { 558 | case ExpressionType.Conditional: 559 | var condExpr = (ConditionalExpression)node; 560 | return condExpr.Type != typeof(void) && IsInline(condExpr.IfTrue) && IsInline(condExpr.IfFalse); 561 | 562 | case ExpressionType.Block: 563 | case ExpressionType.DebugInfo: 564 | //case ExpressionType.Goto: 565 | case ExpressionType.Label: 566 | case ExpressionType.Loop: 567 | case ExpressionType.Switch: 568 | //case ExpressionType.Throw: 569 | case ExpressionType.Try: 570 | return false; 571 | default: 572 | return true; 573 | } 574 | } 575 | 576 | private Expression VisitMultiline(Expression node, bool shouldReturn) 577 | { 578 | switch (node.NodeType) 579 | { 580 | case ExpressionType.Block: 581 | return VisitBlock((BlockExpression)node, shouldReturn); 582 | 583 | case ExpressionType.Conditional: 584 | return VisitConditional((ConditionalExpression)node, shouldReturn); 585 | 586 | case ExpressionType.Try: 587 | return VisitTry((TryExpression)node, shouldReturn); 588 | 589 | case ExpressionType.Switch: 590 | return VisitSwitch((SwitchExpression)node, shouldReturn); 591 | 592 | //case ExpressionType.DebugInfo: 593 | //case ExpressionType.Goto: 594 | //case ExpressionType.Loop: 595 | default: 596 | return Visit(node)!; 597 | } 598 | } 599 | 600 | private Expression VisitBody(Expression node, bool shouldReturn = false) 601 | { 602 | if (node.NodeType == ExpressionType.Block) 603 | return VisitBlock((BlockExpression)node, shouldReturn); 604 | 605 | if (node.NodeType == ExpressionType.Default && node.Type == typeof(void)) 606 | return node; 607 | 608 | var lines = VisitBlockBody(new List { node }, shouldReturn); 609 | return Expression.Block(lines); 610 | } 611 | 612 | private IEnumerable VisitBlockBody(IList exprs, bool shouldReturn) 613 | { 614 | var lines = new List(); 615 | var last = exprs.Count - 1; 616 | for (int i = 0; i < exprs.Count; i++) 617 | { 618 | var expr = exprs[i]; 619 | if (expr.NodeType == ExpressionType.Default && expr.Type == typeof(void)) 620 | continue; 621 | 622 | var isInline = IsInline(expr); 623 | if (isInline || i > 0) 624 | WriteLine(); 625 | 626 | Expression next; 627 | if (isInline) 628 | { 629 | if (shouldReturn && i == last && expr.NodeType != ExpressionType.Throw) 630 | Write("return "); 631 | next = Visit(expr)!; 632 | Write(";"); 633 | } 634 | else 635 | { 636 | next = VisitMultiline(expr, shouldReturn && i == last); 637 | } 638 | lines.Add(next); 639 | } 640 | return lines; 641 | } 642 | 643 | private Expression VisitBlock(BlockExpression node, bool shouldReturn) 644 | { 645 | var assignedVariables = node.Expressions 646 | .Where(exp => exp.NodeType == ExpressionType.Assign) 647 | .Select(exp => ((BinaryExpression)exp).Left) 648 | .Where(exp => exp.NodeType == ExpressionType.Parameter) 649 | .Cast() 650 | .ToHashSet(); 651 | 652 | var list = new List(); 653 | var hasDeclaration = false; 654 | foreach (var variable in node.Variables) 655 | { 656 | Expression arg; 657 | if (assignedVariables.Contains(variable)) 658 | { 659 | arg = VisitParameter(variable, false); 660 | } 661 | else 662 | { 663 | arg = VisitNextLine(Translate(variable.Type) + " ", variable, ";"); 664 | hasDeclaration = true; 665 | } 666 | list.Add((ParameterExpression)arg); 667 | } 668 | if (hasDeclaration) 669 | WriteLine(); 670 | 671 | var lines = VisitBlockBody(node.Expressions, shouldReturn && node.Type != typeof(void)); 672 | return Expression.Block(list, lines); 673 | } 674 | 675 | protected override Expression VisitBlock(BlockExpression node) 676 | { 677 | return VisitBlock(node, false); 678 | } 679 | 680 | private CatchBlock VisitCatchBlock(CatchBlock node, bool shouldReturn) 681 | { 682 | WriteNextLine("catch (", Translate(node.Test)); 683 | if (node.Variable != null) 684 | { 685 | Visit(" ", node.Variable); 686 | } 687 | Write(")"); 688 | 689 | var filter = node.Filter; 690 | if (filter != null) 691 | { 692 | filter = Visit(" when (", filter, ")"); 693 | } 694 | Indent(); 695 | var body = VisitBody(node.Body, shouldReturn); 696 | Outdent(); 697 | return node.Variable != null 698 | ? Expression.Catch(node.Variable, body, filter) 699 | : Expression.Catch(node.Test, body, filter); 700 | } 701 | 702 | protected override CatchBlock VisitCatchBlock(CatchBlock node) 703 | { 704 | return VisitCatchBlock(node, false); 705 | } 706 | 707 | private Expression VisitConditional(ConditionalExpression node, bool shouldReturn) 708 | { 709 | if (IsInline(node)) 710 | { 711 | Expression test = VisitGroup(node.Test, node.NodeType); 712 | Write(" ? "); 713 | Expression ifTrue = VisitGroup(node.IfTrue, node.NodeType); 714 | Write(" : "); 715 | Expression ifFalse = VisitGroup(node.IfFalse, node.NodeType); 716 | return node.Update(test, ifTrue, ifFalse); 717 | } 718 | else 719 | { 720 | return VisitConditionalBlock(node, shouldReturn); 721 | } 722 | } 723 | 724 | private Expression VisitConditionalBlock(ConditionalExpression node, bool shouldReturn, bool chain = false) 725 | { 726 | WriteNextLine(chain ? "else if (" : "if ("); 727 | var test = Visit(node.Test)!; 728 | Write(")"); 729 | Indent(); 730 | Expression ifTrue = VisitBody(node.IfTrue, shouldReturn); 731 | Expression ifFalse = node.IfFalse; 732 | if (node.IfFalse.NodeType != ExpressionType.Default) 733 | { 734 | Outdent(); 735 | if (node.IfFalse.NodeType == ExpressionType.Conditional) 736 | { 737 | ifFalse = VisitConditionalBlock((ConditionalExpression)node.IfFalse, shouldReturn, true); 738 | } 739 | else 740 | { 741 | WriteNextLine("else"); 742 | Indent(); 743 | ifFalse = VisitBody(node.IfFalse, shouldReturn); 744 | Outdent(); 745 | } 746 | } 747 | else 748 | { 749 | Outdent(); 750 | } 751 | 752 | Expression condition = Expression.Condition(test, ifTrue, ifFalse, typeof(void)); 753 | return CreateBlock(condition); 754 | } 755 | 756 | protected override Expression VisitConditional(ConditionalExpression node) 757 | { 758 | return VisitConditional(node, false); 759 | } 760 | 761 | private void WriteValue(object? value) 762 | { 763 | if (value == null) 764 | { 765 | Write("null"); 766 | } 767 | else if (value is string str) 768 | { 769 | if (str.IndexOf('\\') >= 0 || str.IndexOf('\n') >= 0 || str.IndexOf('"') >= 0) 770 | { 771 | str = str.Replace(@"""", @""""""); 772 | Write($"@\"{str}\""); 773 | } 774 | else 775 | { 776 | Write($"\"{str}\""); 777 | } 778 | } 779 | else if (value is char c) 780 | { 781 | if (c == '\\') 782 | Write(@"'\\'"); 783 | else if (c == '\'') 784 | Write(@"'\''"); 785 | else 786 | Write($"'{c}'"); 787 | } 788 | else if (value is bool) 789 | { 790 | Write(value.ToString().ToLower()); 791 | } 792 | else if (value is Type t) 793 | { 794 | Write($"typeof({Translate(t)})"); 795 | } 796 | else if (value is int) 797 | { 798 | Write(value.ToString()); 799 | } 800 | else if (value is double d) 801 | { 802 | if (double.IsNaN(d)) 803 | Write("double.NaN"); 804 | else if (double.IsPositiveInfinity(d)) 805 | Write("double.PositiveInfinity"); 806 | else if (double.IsNegativeInfinity(d)) 807 | Write("double.NegativeInfinity"); 808 | else 809 | Write(d.ToString(CultureInfo.InvariantCulture), "d"); 810 | } 811 | else if (value is float f) 812 | { 813 | if (float.IsNaN(f)) 814 | Write("float.NaN"); 815 | else if (float.IsPositiveInfinity(f)) 816 | Write("float.PositiveInfinity"); 817 | else if (float.IsNegativeInfinity(f)) 818 | Write("float.NegativeInfinity"); 819 | else 820 | Write(f.ToString(CultureInfo.InvariantCulture), "f"); 821 | } 822 | else if (value is decimal || value is long || value is uint || value is ulong) 823 | { 824 | Write(value.ToString(), GetLiteral(value.GetType())); 825 | } 826 | else if (value is byte || value is sbyte || value is short || value is ushort) 827 | { 828 | Write("((", Translate(value.GetType()), ")", value.ToString(), ")"); 829 | } 830 | else if (value.GetType().GetTypeInfo().IsEnum) 831 | { 832 | var name = Enum.GetName(value.GetType(), value); 833 | if (name != null) 834 | Write(Translate(value.GetType()), ".", name); 835 | else 836 | Write("(", Translate(value.GetType()), ")", value.ToString()); 837 | } 838 | else 839 | { 840 | var type = value.GetType(); 841 | if (type.GetTypeInfo().IsValueType) 842 | { 843 | _defaults ??= new Dictionary(); 844 | if (!_defaults.TryGetValue(type, out var def)) 845 | { 846 | def = Activator.CreateInstance(type); 847 | _defaults[type] = def; 848 | } 849 | if (value.Equals(def)) 850 | { 851 | Write($"default({Translate(type)})"); 852 | return; 853 | } 854 | } 855 | Write(GetConstant(value)); 856 | } 857 | } 858 | 859 | protected override Expression VisitConstant(ConstantExpression node) 860 | { 861 | WriteValue(node.Value); 862 | return node; 863 | } 864 | 865 | private static string GetLiteral(Type type) 866 | { 867 | if (type == typeof(decimal)) 868 | return "m"; 869 | else if (type == typeof(long)) 870 | return "l"; 871 | else if (type == typeof(uint)) 872 | return "u"; 873 | else if (type == typeof(ulong)) 874 | return "ul"; 875 | else if (type == typeof(double)) 876 | return "d"; 877 | else if (type == typeof(float)) 878 | return "f"; 879 | else 880 | return ""; 881 | } 882 | 883 | protected override Expression VisitDefault(DefaultExpression node) 884 | { 885 | Write("default(", Translate(node.Type), ")"); 886 | return node; 887 | } 888 | 889 | #if !NETSTANDARD1_3 890 | private static Expression Update(DynamicExpression node, IEnumerable args) 891 | { 892 | // ReSharper disable PossibleMultipleEnumeration 893 | return node.Arguments.SequenceEqual(args) ? node : node.Update(args); 894 | // ReSharper restore PossibleMultipleEnumeration 895 | } 896 | 897 | protected override Expression VisitDynamic(DynamicExpression node) 898 | { 899 | if (node.Binder is ConvertBinder convert) 900 | { 901 | Write("(", Translate(convert.Type), ")"); 902 | var expr = VisitGroup(node.Arguments[0], ExpressionType.Convert); 903 | return Update(node, new[] { expr }.Concat(node.Arguments.Skip(1))); 904 | } 905 | if (node.Binder is GetMemberBinder getMember) 906 | { 907 | var expr = VisitGroup(node.Arguments[0], ExpressionType.MemberAccess); 908 | Write(".", getMember.Name); 909 | return Update(node, new[] { expr }.Concat(node.Arguments.Skip(1))); 910 | } 911 | if (node.Binder is SetMemberBinder setMember) 912 | { 913 | var expr = VisitGroup(node.Arguments[0], ExpressionType.MemberAccess); 914 | Write(".", setMember.Name, " = "); 915 | var value = VisitGroup(node.Arguments[1], ExpressionType.Assign); 916 | return Update(node, new[] { expr, value }.Concat(node.Arguments.Skip(2))); 917 | } 918 | if (node.Binder is DeleteMemberBinder deleteMember) 919 | { 920 | var expr = VisitGroup(node.Arguments[0], ExpressionType.MemberAccess); 921 | Write(".", deleteMember.Name, " = null"); 922 | return Update(node, new[] { expr }.Concat(node.Arguments.Skip(1))); 923 | } 924 | if (node.Binder is GetIndexBinder) 925 | { 926 | var expr = VisitGroup(node.Arguments[0], ExpressionType.Index); 927 | var args = VisitArguments("[", node.Arguments.Skip(1).ToList(), Visit, "]"); 928 | return Update(node, new[] { expr }.Concat(args)); 929 | } 930 | if (node.Binder is SetIndexBinder) 931 | { 932 | var expr = VisitGroup(node.Arguments[0], ExpressionType.Index); 933 | var args = VisitArguments("[", node.Arguments.Skip(1).Take(node.Arguments.Count - 2).ToList(), Visit, "]"); 934 | Write(" = "); 935 | var value = VisitGroup(node.Arguments[node.Arguments.Count - 1], ExpressionType.Assign); 936 | return Update(node, new[] { expr }.Concat(args).Concat(new[] { value })); 937 | } 938 | if (node.Binder is DeleteIndexBinder) 939 | { 940 | var expr = VisitGroup(node.Arguments[0], ExpressionType.Index); 941 | var args = VisitArguments("[", node.Arguments.Skip(1).ToList(), Visit, "]"); 942 | Write(" = null"); 943 | return Update(node, new[] { expr }.Concat(args)); 944 | } 945 | if (node.Binder is InvokeMemberBinder invokeMember) 946 | { 947 | var expr = VisitGroup(node.Arguments[0], ExpressionType.MemberAccess); 948 | Write(".", invokeMember.Name); 949 | var args = VisitArguments("(", node.Arguments.Skip(1).ToList(), Visit, ")"); 950 | return Update(node, new[] { expr }.Concat(args)); 951 | } 952 | if (node.Binder is InvokeBinder) 953 | { 954 | var expr = VisitGroup(node.Arguments[0], ExpressionType.Invoke); 955 | var args = VisitArguments("(", node.Arguments.Skip(1).ToList(), Visit, ")"); 956 | return Update(node, new[] { expr }.Concat(args)); 957 | } 958 | if (node.Binder is CreateInstanceBinder) 959 | { 960 | Write("new "); 961 | var expr = VisitGroup(node.Arguments[0], ExpressionType.Invoke); 962 | var args = VisitArguments("(", node.Arguments.Skip(1).ToList(), Visit, ")"); 963 | return Update(node, new[] { expr }.Concat(args)); 964 | } 965 | if (node.Binder is UnaryOperationBinder unary) 966 | { 967 | var expr = VisitUnary(node.Arguments[0], unary.Operation); 968 | return Update(node, new[] { expr }.Concat(node.Arguments.Skip(1))); 969 | } 970 | if (node.Binder is BinaryOperationBinder binary) 971 | { 972 | var left = VisitGroup(node.Arguments[0], node.NodeType); 973 | Write(" ", Translate(binary.Operation), " "); 974 | var right = VisitGroup(node.Arguments[1], node.NodeType, true); 975 | return Update(node, new[] { left, right }.Concat(node.Arguments.Skip(2))); 976 | } 977 | Write("dynamic"); 978 | var dynArgs = VisitArguments("(" + Translate(node.Binder.GetType()) + ", ", node.Arguments, Visit, ")"); 979 | return node.Update(dynArgs); 980 | } 981 | #endif 982 | 983 | private IList VisitArguments(string open, IList args, Func func, string end, bool wrap = false, IList? prefix = null) where T : class 984 | { 985 | Write(open); 986 | if (wrap) 987 | _indentLevel++; 988 | 989 | var list = new List(); 990 | var last = args.Count - 1; 991 | var changed = false; 992 | for (var i = 0; i < args.Count; i++) 993 | { 994 | if (wrap) 995 | WriteLine(); 996 | if (prefix != null) 997 | Write(prefix[i]); 998 | var arg = func(args[i]); 999 | changed |= arg != args[i]; 1000 | list.Add(arg); 1001 | if (i != last) 1002 | Write(wrap ? "," : ", "); 1003 | } 1004 | if (wrap) 1005 | { 1006 | _indentLevel--; 1007 | WriteLine(); 1008 | } 1009 | Write(end); 1010 | return changed ? list : args; 1011 | } 1012 | 1013 | protected override ElementInit VisitElementInit(ElementInit node) 1014 | { 1015 | if (node.Arguments.Count == 1) 1016 | { 1017 | var arg = Visit(node.Arguments[0]); 1018 | var args = arg != node.Arguments[0] ? new[] { arg }.AsEnumerable() : node.Arguments; 1019 | return node.Update(args); 1020 | } 1021 | else 1022 | { 1023 | var list = VisitArguments("{", node.Arguments, Visit, "}"); 1024 | return node.Update(list); 1025 | } 1026 | } 1027 | 1028 | private Dictionary? _counter; 1029 | private Dictionary? _ids; 1030 | 1031 | private string GetConstant(object obj, string? typeName = null) 1032 | { 1033 | if (this.Constants.TryGetValue(obj, out var name)) 1034 | return name; 1035 | 1036 | _counter ??= new Dictionary(); 1037 | 1038 | typeName ??= GetVarName(obj.GetType().Name); 1039 | 1040 | _counter.TryGetValue(typeName, out var id); 1041 | id++; 1042 | _counter[typeName] = id; 1043 | name = typeName + id; 1044 | this.Constants[obj] = name; 1045 | return name; 1046 | } 1047 | 1048 | private string GetName(string type, object obj) 1049 | { 1050 | _ids ??= new Dictionary(); 1051 | 1052 | if (_ids.TryGetValue(obj, out int id)) 1053 | return type + id; 1054 | 1055 | _counter ??= new Dictionary(); 1056 | _counter.TryGetValue(type, out id); 1057 | id++; 1058 | _counter[type] = id; 1059 | _ids[obj] = id; 1060 | return type + id; 1061 | } 1062 | 1063 | private string GetName(LabelTarget label) 1064 | { 1065 | return string.IsNullOrEmpty(label.Name) ? GetName("label", label) : label.Name; 1066 | } 1067 | 1068 | private string GetName(ParameterExpression param) 1069 | { 1070 | if (string.IsNullOrEmpty(param.Name)) 1071 | return GetName("p", param); 1072 | else if (ReservedWords.Contains(param.Name)) 1073 | return "@" + param.Name; 1074 | else 1075 | return param.Name; 1076 | } 1077 | 1078 | private string GetName(LambdaExpression lambda, string defaultMethodName = "Main") 1079 | { 1080 | var main = Definitions?.TypeName != null 1081 | ? defaultMethodName 1082 | : ""; 1083 | return string.IsNullOrEmpty(lambda.Name) ? GetName("func" + main, lambda) : lambda.Name; 1084 | } 1085 | 1086 | private HashSet? _returnTargets; 1087 | protected override Expression VisitGoto(GotoExpression node) 1088 | { 1089 | switch (node.Kind) 1090 | { 1091 | case GotoExpressionKind.Goto: 1092 | Write("goto ", GetName(node.Target)); 1093 | break; 1094 | case GotoExpressionKind.Return: 1095 | _returnTargets ??= new HashSet(); 1096 | _returnTargets.Add(node.Target); 1097 | var value = Visit("return ", node.Value); 1098 | return node.Update(node.Target, value); 1099 | case GotoExpressionKind.Break: 1100 | Write("break"); 1101 | break; 1102 | case GotoExpressionKind.Continue: 1103 | Write("continue"); 1104 | break; 1105 | default: 1106 | throw new ArgumentOutOfRangeException(nameof(node)); 1107 | } 1108 | return node; 1109 | } 1110 | 1111 | private Expression? VisitMember(Expression? instance, Expression node, MemberInfo member) 1112 | { 1113 | if (instance != null) 1114 | { 1115 | var result = VisitGroup(instance, node.NodeType); 1116 | Write(".", member.Name); 1117 | return result; 1118 | } 1119 | else 1120 | { 1121 | Write(Translate(member.DeclaringType!), ".", member.Name); 1122 | return null; 1123 | } 1124 | } 1125 | 1126 | protected override Expression VisitIndex(IndexExpression node) 1127 | { 1128 | var obj = node.Indexer != null && node.Indexer.DeclaringType!.GetCustomAttribute()?.MemberName != node.Indexer.Name 1129 | ? VisitMember(node.Object, node, node.Indexer) 1130 | : VisitGroup(node.Object, node.NodeType); 1131 | 1132 | var args = VisitArguments("[", node.Arguments, Visit, "]"); 1133 | 1134 | return node.Update(obj!, args); 1135 | } 1136 | 1137 | protected override Expression VisitInvocation(InvocationExpression node) 1138 | { 1139 | var exp = VisitGroup(node.Expression, node.NodeType); 1140 | var args = VisitArguments("(", node.Arguments, Visit, ")"); 1141 | return node.Update(exp, args); 1142 | } 1143 | 1144 | protected override Expression VisitLabel(LabelExpression node) 1145 | { 1146 | if (_returnTargets == null || !_returnTargets.Contains(node.Target)) 1147 | Write(GetName(node.Target), ":"); 1148 | return node; 1149 | } 1150 | 1151 | private ParameterExpression VisitParameterDeclaration(ParameterExpression node) 1152 | { 1153 | if (node.Type.IsByRef) 1154 | Write("ref "); 1155 | return (ParameterExpression)Visit(Translate(node.Type) + " ", node); 1156 | } 1157 | 1158 | private void WriteModifier(string modifier) 1159 | { 1160 | Write(modifier, " "); 1161 | if (Definitions?.IsStatic == true) 1162 | Write("static "); 1163 | } 1164 | private void WriteModifierNextLine(string modifier) 1165 | { 1166 | WriteLine(); 1167 | WriteModifier(modifier); 1168 | } 1169 | 1170 | private int _inlineCount; 1171 | public Expression VisitLambda(LambdaExpression node, LambdaType type, string? methodName = null, bool isInternal = false) 1172 | { 1173 | if (type == LambdaType.PrivateLambda || type == LambdaType.PublicLambda) 1174 | { 1175 | _inlineCount++; 1176 | if (type == LambdaType.PublicLambda) 1177 | { 1178 | var name = methodName ?? "Main"; 1179 | if (!isInternal) 1180 | isInternal = node.ReturnType.GetTypeInfo().IsNotPublic || node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic); 1181 | WriteModifierNextLine(isInternal ? "internal" : "public"); 1182 | var funcType = MakeDelegateType(node.ReturnType, node.Parameters.Select(it => it.Type).ToArray()); 1183 | var exprType = typeof(Expression<>).MakeGenericType(funcType); 1184 | Write(Translate(exprType), " ", name, " => "); 1185 | } 1186 | IList args; 1187 | if (node.Parameters.Count == 1) 1188 | { 1189 | args = new List(); 1190 | var arg = VisitParameter(node.Parameters[0]); 1191 | args.Add((ParameterExpression) arg); 1192 | } 1193 | else 1194 | { 1195 | args = VisitArguments("(", node.Parameters.ToList(), p => (ParameterExpression) VisitParameter(p),")"); 1196 | } 1197 | 1198 | Write(" => "); 1199 | var body = VisitGroup(node.Body, ExpressionType.Quote); 1200 | if (type == LambdaType.PublicLambda) 1201 | Write(";"); 1202 | _inlineCount--; 1203 | return Expression.Lambda(body, node.Name, node.TailCall, args); 1204 | } 1205 | else 1206 | { 1207 | var name = methodName ?? "Main"; 1208 | if (type == LambdaType.PublicMethod || type == LambdaType.ExtensionMethod) 1209 | { 1210 | if (!isInternal) 1211 | isInternal = node.ReturnType.GetTypeInfo().IsNotPublic || node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic); 1212 | WriteModifierNextLine(isInternal ? "internal" : "public"); 1213 | this.Methods[name] = node.Type; 1214 | } 1215 | else 1216 | { 1217 | name = GetName(node, name); 1218 | WriteModifierNextLine("private"); 1219 | } 1220 | Write(Translate(node.ReturnType), " ", name); 1221 | var open = "("; 1222 | if (type == LambdaType.ExtensionMethod) 1223 | { 1224 | if (this.Definitions?.IsStatic != true) 1225 | throw new InvalidOperationException("Extension method requires static class"); 1226 | if (node.Parameters.Count == 0) 1227 | throw new InvalidOperationException("Extension method requires at least 1 parameter"); 1228 | open = "(this "; 1229 | } 1230 | var args = VisitArguments(open, node.Parameters, VisitParameterDeclaration, ")"); 1231 | Indent(); 1232 | var body = VisitBody(node.Body, true); 1233 | 1234 | Outdent(); 1235 | 1236 | return Expression.Lambda(body, name, node.TailCall, args); 1237 | } 1238 | } 1239 | 1240 | private HashSet? _visitedLambda; 1241 | private int _writerLevel; 1242 | protected override Expression VisitLambda(Expression node) 1243 | { 1244 | if (_inlineCount > 0) 1245 | return VisitLambda(node, LambdaType.PrivateLambda); 1246 | 1247 | Write(GetName(node)); 1248 | 1249 | _visitedLambda ??= new HashSet(); 1250 | if (_visitedLambda.Contains(node)) 1251 | return node; 1252 | _visitedLambda.Add(node); 1253 | 1254 | //switch writer to append writer 1255 | _appendWriters ??= new List(); 1256 | if (_writerLevel == _appendWriters.Count) 1257 | _appendWriters.Add(new StringWriter()); 1258 | 1259 | var temp = _writer; 1260 | var oldIndent = _indentLevel; 1261 | try 1262 | { 1263 | _writer = _appendWriters[_writerLevel]; 1264 | _writerLevel++; 1265 | ResetIndentLevel(); 1266 | 1267 | WriteLine(); 1268 | return VisitLambda(node, LambdaType.PrivateMethod); 1269 | } 1270 | finally 1271 | { 1272 | //switch back 1273 | _writer = temp; 1274 | _indentLevel = oldIndent; 1275 | _writerLevel--; 1276 | } 1277 | } 1278 | 1279 | private IList VisitElements(IList list, Func func) where T : class 1280 | { 1281 | var wrap = true; 1282 | if (list.Count == 0) 1283 | { 1284 | wrap = false; 1285 | } 1286 | else if (list.Count <= 4) 1287 | { 1288 | wrap = list[0] is MemberBinding && list.Count > 1; 1289 | } 1290 | if (wrap) 1291 | WriteLine(); 1292 | else 1293 | Write(" "); 1294 | return VisitArguments("{", list, func, "}", wrap); 1295 | } 1296 | 1297 | protected override Expression VisitListInit(ListInitExpression node) 1298 | { 1299 | var @new = (NewExpression)Visit(node.NewExpression)!; 1300 | var args = VisitElements(node.Initializers, VisitElementInit); 1301 | return node.Update(@new, args); 1302 | } 1303 | 1304 | protected override Expression VisitLoop(LoopExpression node) 1305 | { 1306 | Expression body; 1307 | if (node.Body.NodeType == ExpressionType.Conditional) 1308 | { 1309 | var condExpr = (ConditionalExpression)node.Body; 1310 | 1311 | if (condExpr.IfFalse is GotoExpression @break && @break.Target == node.BreakLabel) 1312 | { 1313 | WriteNextLine("while ("); 1314 | var test = Visit(condExpr.Test)!; 1315 | Write(")"); 1316 | Indent(); 1317 | body = VisitBody(condExpr.IfTrue); 1318 | Outdent(); 1319 | var outBreak = CreateBlock(@break); 1320 | 1321 | Expression condition = Expression.Condition(test, body, outBreak, typeof(void)); 1322 | condition = CreateBlock(condition); 1323 | return Expression.Loop( 1324 | condition, 1325 | node.BreakLabel, 1326 | node.ContinueLabel); 1327 | } 1328 | } 1329 | 1330 | WriteNextLine("while (true)"); 1331 | Indent(); 1332 | body = VisitBody(node.Body); 1333 | Outdent(); 1334 | return Expression.Loop(body, node.BreakLabel, node.ContinueLabel); 1335 | } 1336 | 1337 | protected override Expression VisitMember(MemberExpression node) 1338 | { 1339 | var expr = VisitMember(node.Expression, node, node.Member)!; 1340 | return node.Update(expr); 1341 | } 1342 | 1343 | protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) 1344 | { 1345 | Write(node.Member.Name, " = "); 1346 | var expr = Visit(node.Expression)!; 1347 | return node.Update(expr); 1348 | } 1349 | 1350 | protected override Expression VisitMemberInit(MemberInitExpression node) 1351 | { 1352 | var @new = (NewExpression)Visit(node.NewExpression)!; 1353 | var args = VisitElements(node.Bindings, VisitMemberBinding); 1354 | return node.Update(@new, args); 1355 | } 1356 | 1357 | protected override MemberListBinding VisitMemberListBinding(MemberListBinding node) 1358 | { 1359 | Write(node.Member.Name, " ="); 1360 | var args = VisitElements(node.Initializers, VisitElementInit); 1361 | return node.Update(args); 1362 | } 1363 | 1364 | protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node) 1365 | { 1366 | Write(node.Member.Name, " ="); 1367 | var args = VisitElements(node.Bindings, VisitMemberBinding); 1368 | return node.Update(args); 1369 | } 1370 | 1371 | private static Type MakeDelegateType(Type returnType, params Type[] parameters) 1372 | { 1373 | var del = GetDelegateType(returnType != typeof(void), parameters.Length); 1374 | if (del.GetTypeInfo().IsGenericTypeDefinition) 1375 | { 1376 | var types = parameters.AsEnumerable(); 1377 | if (returnType != typeof(void)) 1378 | types = types.Concat(new[] { returnType }); 1379 | del = del.MakeGenericType(types.ToArray()); 1380 | } 1381 | 1382 | return del; 1383 | } 1384 | 1385 | private static Type GetDelegateType(bool isFunc, int argCount) 1386 | { 1387 | if (!isFunc) 1388 | { 1389 | switch (argCount) 1390 | { 1391 | case 0: return typeof(Action); 1392 | case 1: return typeof(Action<>); 1393 | case 2: return typeof(Action<,>); 1394 | case 3: return typeof(Action<,,>); 1395 | case 4: return typeof(Action<,,,>); 1396 | case 5: return typeof(Action<,,,,>); 1397 | case 6: return typeof(Action<,,,,,>); 1398 | case 7: return typeof(Action<,,,,,,>); 1399 | case 8: return typeof(Action<,,,,,,,>); 1400 | case 9: return typeof(Action<,,,,,,,,>); 1401 | case 10: return typeof(Action<,,,,,,,,,>); 1402 | case 11: return typeof(Action<,,,,,,,,,,>); 1403 | case 12: return typeof(Action<,,,,,,,,,,,>); 1404 | case 13: return typeof(Action<,,,,,,,,,,,,>); 1405 | case 14: return typeof(Action<,,,,,,,,,,,,,>); 1406 | case 15: return typeof(Action<,,,,,,,,,,,,,,>); 1407 | case 16: return typeof(Action<,,,,,,,,,,,,,,,>); 1408 | default: throw new InvalidOperationException("Cannot handle non-public method"); 1409 | } 1410 | } 1411 | else 1412 | { 1413 | switch (argCount) 1414 | { 1415 | case 0: return typeof(Func<>); 1416 | case 1: return typeof(Func<,>); 1417 | case 2: return typeof(Func<,,>); 1418 | case 3: return typeof(Func<,,,>); 1419 | case 4: return typeof(Func<,,,,>); 1420 | case 5: return typeof(Func<,,,,,>); 1421 | case 6: return typeof(Func<,,,,,,>); 1422 | case 7: return typeof(Func<,,,,,,,>); 1423 | case 8: return typeof(Func<,,,,,,,,>); 1424 | case 9: return typeof(Func<,,,,,,,,,>); 1425 | case 10: return typeof(Func<,,,,,,,,,,>); 1426 | case 11: return typeof(Func<,,,,,,,,,,,>); 1427 | case 12: return typeof(Func<,,,,,,,,,,,,>); 1428 | case 13: return typeof(Func<,,,,,,,,,,,,,>); 1429 | case 14: return typeof(Func<,,,,,,,,,,,,,,>); 1430 | case 15: return typeof(Func<,,,,,,,,,,,,,,,>); 1431 | case 16: return typeof(Func<,,,,,,,,,,,,,,,,>); 1432 | default: throw new InvalidOperationException("Cannot handle non-public method"); 1433 | } 1434 | } 1435 | } 1436 | 1437 | protected override Expression VisitMethodCall(MethodCallExpression node) 1438 | { 1439 | var isExtension = false; 1440 | var isNotPublic = false; 1441 | Expression? arg0 = null; 1442 | 1443 | var obj = node.Object; 1444 | if (obj != null) 1445 | { 1446 | obj = VisitGroup(node.Object!, node.NodeType); 1447 | } 1448 | #if !NET40 1449 | else if (!node.Method.IsPublic || node.Method.DeclaringType?.GetTypeInfo().IsNotPublic == true) 1450 | { 1451 | isNotPublic = true; 1452 | if (node.Method.GetParameters().Any(it => it.IsOut || it.ParameterType.IsByRef)) 1453 | throw new InvalidOperationException("Cannot handle non-public method"); 1454 | 1455 | var del = MakeDelegateType(node.Method.ReturnType, node.Method.GetParameters().Select(it => it.ParameterType).ToArray()); 1456 | var func = node.Method.CreateDelegate(del); 1457 | Write(GetConstant(func, GetVarName(node.Method.Name)), ".Invoke"); 1458 | } 1459 | #endif 1460 | else if (node.Method.GetCustomAttribute() != null) 1461 | { 1462 | isExtension = true; 1463 | arg0 = VisitGroup(node.Arguments[0], node.NodeType); 1464 | if (!string.IsNullOrEmpty(node.Method.DeclaringType?.Namespace)) 1465 | { 1466 | _usings ??= new HashSet(); 1467 | _usings.Add(node.Method.DeclaringType!.Namespace); 1468 | } 1469 | } 1470 | else if (node.Method.DeclaringType != null) 1471 | { 1472 | Write(Translate(node.Method.DeclaringType)); 1473 | } 1474 | 1475 | if (node.Method.IsSpecialName && node.Method.Name.StartsWith("get_")) 1476 | { 1477 | var attr = node.Method.DeclaringType!.GetCustomAttribute(); 1478 | if (attr?.MemberName == node.Method.Name.Substring(4)) 1479 | { 1480 | var keys = VisitArguments("[", node.Arguments, Visit, "]"); 1481 | return node.Update(obj, keys); 1482 | } 1483 | } 1484 | 1485 | if (!isNotPublic) 1486 | { 1487 | if (node.Method.DeclaringType != null) 1488 | Write("."); 1489 | Write(node.Method.Name); 1490 | if (node.Method.IsGenericMethod) 1491 | { 1492 | var args = string.Join(", ", node.Method.GetGenericArguments().Select(Translate)); 1493 | Write("<", args, ">"); 1494 | } 1495 | } 1496 | var prefix = node.Method.GetParameters() 1497 | .Select(p => p.IsOut ? "out " : p.ParameterType.IsByRef ? "ref " : ""); 1498 | 1499 | if (isExtension) 1500 | { 1501 | var args = VisitArguments("(", node.Arguments.Skip(1).ToList(), Visit, ")", prefix: prefix.Skip(1).ToList()); 1502 | var newArgs = new[] { arg0 }.Concat(args).ToList(); 1503 | return newArgs.SequenceEqual(node.Arguments) ? node : node.Update(obj, newArgs); 1504 | } 1505 | else 1506 | { 1507 | var args = VisitArguments("(", node.Arguments, Visit, ")", prefix: prefix.ToList()); 1508 | return node.Update(obj, args); 1509 | } 1510 | } 1511 | 1512 | protected override Expression VisitNew(NewExpression node) 1513 | { 1514 | Write("new ", Translate(node.Type)); 1515 | var args = VisitArguments("(", node.Arguments, Visit, ")"); 1516 | return node.Update(args); 1517 | } 1518 | 1519 | protected override Expression VisitNewArray(NewArrayExpression node) 1520 | { 1521 | if (node.NodeType == ExpressionType.NewArrayBounds) 1522 | { 1523 | var elemType = node.Type.GetElementType(); 1524 | var arrayCount = 1; 1525 | // ReSharper disable once PossibleNullReferenceException 1526 | while (elemType.IsArray) 1527 | { 1528 | elemType = elemType.GetElementType(); 1529 | arrayCount++; 1530 | } 1531 | Write("new ", Translate(elemType)); 1532 | var args = VisitArguments("[", node.Expressions, Visit, "]"); 1533 | for (int i = 1; i < arrayCount; i++) 1534 | Write("[]"); 1535 | return node.Update(args); 1536 | } 1537 | else 1538 | { 1539 | Write("new ", Translate(node.Type)); 1540 | var args = VisitElements(node.Expressions, Visit); 1541 | return node.Update(args); 1542 | } 1543 | } 1544 | 1545 | #region _reservedWords 1546 | private static readonly HashSet ReservedWords = new HashSet 1547 | { 1548 | "abstract", 1549 | "as", 1550 | "base", 1551 | "bool", 1552 | "break", 1553 | "by", 1554 | "byte", 1555 | "case", 1556 | "catch", 1557 | "char", 1558 | "checked", 1559 | "class", 1560 | "const", 1561 | "continue", 1562 | "decimal", 1563 | "default", 1564 | "delegate", 1565 | "descending", 1566 | "do", 1567 | "double", 1568 | "else", 1569 | "enum", 1570 | "event", 1571 | "explicit", 1572 | "extern", 1573 | "finally", 1574 | "fixed", 1575 | "float", 1576 | "for", 1577 | "foreach", 1578 | "from", 1579 | "goto", 1580 | "group", 1581 | "if", 1582 | "implicit", 1583 | "in", 1584 | "int", 1585 | "interface", 1586 | "internal", 1587 | "into", 1588 | "is", 1589 | "lock", 1590 | "long", 1591 | "namespace", 1592 | "new", 1593 | "null", 1594 | "object", 1595 | "operator", 1596 | "orderby", 1597 | "out", 1598 | "override", 1599 | "params", 1600 | "private", 1601 | "protected", 1602 | "public", 1603 | "readonly", 1604 | "ref", 1605 | "return", 1606 | "sbyte", 1607 | "sealed", 1608 | "select", 1609 | "short", 1610 | "sizeof", 1611 | "stackalloc", 1612 | "static", 1613 | "string", 1614 | "struct", 1615 | "switch", 1616 | "this", 1617 | "throw", 1618 | "try", 1619 | "typeof", 1620 | "ulong", 1621 | "unchecked", 1622 | "unit", 1623 | "unsafe", 1624 | "ushort", 1625 | "using", 1626 | "var", 1627 | "virtual", 1628 | "void", 1629 | "volatile", 1630 | "where", 1631 | "while", 1632 | "yield", 1633 | "false", 1634 | "true", 1635 | }; 1636 | #endregion 1637 | 1638 | protected override Expression VisitParameter(ParameterExpression node) 1639 | { 1640 | return VisitParameter(node, true); 1641 | } 1642 | 1643 | private HashSet? _pendingVariables; 1644 | private Dictionary? _params; 1645 | private Expression VisitParameter(ParameterExpression node, bool write) 1646 | { 1647 | _pendingVariables ??= new HashSet(); 1648 | 1649 | var name = GetName(node); 1650 | if (write) 1651 | { 1652 | if (_pendingVariables.Contains(node)) 1653 | { 1654 | Write(Translate(node.Type), " ", name); 1655 | _pendingVariables.Remove(node); 1656 | } 1657 | else 1658 | { 1659 | Write(name); 1660 | } 1661 | } 1662 | else 1663 | { 1664 | _pendingVariables.Add(node); 1665 | } 1666 | 1667 | if (!string.IsNullOrEmpty(node.Name)) 1668 | return node; 1669 | 1670 | _params ??= new Dictionary(); 1671 | if (!_params.TryGetValue(node, out var result)) 1672 | { 1673 | result = Expression.Parameter(node.Type, name); 1674 | _params[node] = result; 1675 | } 1676 | return result; 1677 | } 1678 | 1679 | protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) 1680 | { 1681 | Write(GetConstant(node, "RuntimeVariables")); 1682 | return node; 1683 | } 1684 | 1685 | private Expression VisitSwitch(SwitchExpression node, bool shouldReturn) 1686 | { 1687 | WriteNextLine("switch ("); 1688 | var value = Visit(node.SwitchValue)!; 1689 | Write(")"); 1690 | Indent(); 1691 | 1692 | var cases = node.Cases.Select(c => VisitSwitchCase(c, shouldReturn)).ToList(); 1693 | var @default = node.DefaultBody; 1694 | if (@default != null) 1695 | { 1696 | WriteNextLine("default:"); 1697 | _indentLevel++; 1698 | @default = VisitBody(node.DefaultBody, shouldReturn); 1699 | if (!shouldReturn) 1700 | { 1701 | WriteLine(); 1702 | Write("break;"); 1703 | } 1704 | _indentLevel--; 1705 | Outdent(); 1706 | @default = CreateBlock(@default); 1707 | } 1708 | else 1709 | { 1710 | Outdent(); 1711 | } 1712 | 1713 | node = node.Update(value, cases, @default); 1714 | return CreateBlock(node); 1715 | } 1716 | 1717 | private static BlockExpression CreateBlock(params Expression[] exprs) 1718 | { 1719 | return Expression.Block(exprs.Where(expr => expr != null)); 1720 | } 1721 | 1722 | protected override Expression VisitSwitch(SwitchExpression node) 1723 | { 1724 | return VisitSwitch(node, false); 1725 | } 1726 | 1727 | private SwitchCase VisitSwitchCase(SwitchCase node, bool shouldReturn) 1728 | { 1729 | var values = node.TestValues.Select(test => VisitNextLine("case ", test, ":")).ToList(); 1730 | _indentLevel++; 1731 | var body = VisitBody(node.Body, shouldReturn); 1732 | if (!shouldReturn) 1733 | { 1734 | WriteLine(); 1735 | Write("break;"); 1736 | body = CreateBlock(body); 1737 | } 1738 | _indentLevel--; 1739 | return node.Update(values, body); 1740 | } 1741 | 1742 | protected override SwitchCase VisitSwitchCase(SwitchCase node) 1743 | { 1744 | return VisitSwitchCase(node, false); 1745 | } 1746 | 1747 | private Expression VisitTry(TryExpression node, bool shouldReturn) 1748 | { 1749 | string? faultParam = null; 1750 | if (node.Fault != null) 1751 | { 1752 | faultParam = GetName("fault", node); 1753 | WriteNextLine("bool ", faultParam, " = true;"); 1754 | } 1755 | WriteNextLine("try"); 1756 | Indent(); 1757 | var body = VisitBody(node.Body, shouldReturn); 1758 | if (node.Fault != null) 1759 | WriteNextLine(faultParam!, " = false;"); 1760 | Outdent(); 1761 | var handlers = node.Handlers.Select(c => VisitCatchBlock(c, shouldReturn)).ToList(); 1762 | var @finally = node.Finally; 1763 | var fault = node.Fault; 1764 | if (node.Finally != null || node.Fault != null) 1765 | { 1766 | WriteNextLine("finally"); 1767 | Indent(); 1768 | if (node.Finally != null) 1769 | @finally = VisitBody(node.Finally); 1770 | if (node.Fault != null) 1771 | { 1772 | WriteNextLine("if (", faultParam!, ")"); 1773 | Indent(); 1774 | fault = VisitBody(node.Fault); 1775 | Outdent(); 1776 | } 1777 | Outdent(); 1778 | } 1779 | return node.Update(body, handlers, @finally, fault); 1780 | } 1781 | 1782 | protected override Expression VisitTry(TryExpression node) 1783 | { 1784 | return VisitTry(node, false); 1785 | } 1786 | 1787 | protected override Expression VisitTypeBinary(TypeBinaryExpression node) 1788 | { 1789 | var expr = VisitGroup(node.Expression, node.NodeType); 1790 | Write(" is ", Translate(node.TypeOperand)); 1791 | return node.Update(expr); 1792 | } 1793 | 1794 | private Expression VisitUnary(Expression operand, ExpressionType nodeType) 1795 | { 1796 | switch (nodeType) 1797 | { 1798 | case ExpressionType.IsFalse: 1799 | case ExpressionType.Negate: 1800 | case ExpressionType.NegateChecked: 1801 | case ExpressionType.Not: 1802 | case ExpressionType.PreDecrementAssign: 1803 | case ExpressionType.PreIncrementAssign: 1804 | case ExpressionType.OnesComplement: 1805 | case ExpressionType.UnaryPlus: 1806 | Write(Translate(nodeType)); 1807 | break; 1808 | } 1809 | 1810 | var result = VisitGroup(operand, nodeType); 1811 | 1812 | switch (nodeType) 1813 | { 1814 | case ExpressionType.ArrayLength: 1815 | case ExpressionType.Decrement: 1816 | case ExpressionType.Increment: 1817 | case ExpressionType.PostDecrementAssign: 1818 | case ExpressionType.PostIncrementAssign: 1819 | Write(Translate(nodeType)); 1820 | break; 1821 | } 1822 | return result; 1823 | } 1824 | 1825 | protected override Expression VisitUnary(UnaryExpression node) 1826 | { 1827 | switch (node.NodeType) 1828 | { 1829 | case ExpressionType.Convert: 1830 | case ExpressionType.ConvertChecked: 1831 | //if (!node.Type.IsAssignableFrom(node.Operand.Type)) 1832 | Write("(", Translate(node.Type), ")"); 1833 | break; 1834 | 1835 | case ExpressionType.Throw: 1836 | // ReSharper disable once ConditionIsAlwaysTrueOrFalse 1837 | // ReSharper disable HeuristicUnreachableCode 1838 | if (node.Operand == null) 1839 | { 1840 | Write("throw"); 1841 | return node; 1842 | } 1843 | // ReSharper restore HeuristicUnreachableCode 1844 | Write("throw "); 1845 | break; 1846 | } 1847 | 1848 | var operand = node.NodeType == ExpressionType.Quote && node.Operand.NodeType == ExpressionType.Lambda 1849 | ? VisitLambda((LambdaExpression)node.Operand, LambdaType.PrivateLambda) 1850 | : VisitUnary(node.Operand, node.NodeType); 1851 | 1852 | switch (node.NodeType) 1853 | { 1854 | case ExpressionType.TypeAs: 1855 | Write(" as ", Translate(node.Type)); 1856 | break; 1857 | } 1858 | return node.Update(operand); 1859 | } 1860 | 1861 | public override string ToString() 1862 | { 1863 | var codeWriter = new StringWriter(); 1864 | var temp = _writer; 1865 | var oldIndent = _indentLevel; 1866 | _indentLevel = 0; 1867 | 1868 | try 1869 | { 1870 | _writer = codeWriter; 1871 | 1872 | //exercise to update _usings 1873 | var implements = Definitions?.Implements?.OrderBy(it => it.GetTypeInfo().IsInterface ? 1 : 0) 1874 | .Select(Translate) 1875 | .ToList(); 1876 | var constants = _constants?.OrderBy(it => it.Value) 1877 | .Select(kvp => $"{Translate(kvp.Key.GetType())} {kvp.Value};") 1878 | .ToList(); 1879 | var properties = _properties? 1880 | .ToDictionary(it => it.Name, 1881 | it => $"{TranslateNullable(it.Type, it.NullableContext ?? Definitions?.NullableContext, it.Nullable)} {it.Name} {{ get; {(it.IsReadOnly ? "" : it.IsInitOnly ? "init; " : "set; ")}}}"); 1882 | var ctorParams = _properties?.Where(it => it.IsReadOnly).ToList(); 1883 | if (Definitions?.TypeName != null) 1884 | { 1885 | if (_usings != null) 1886 | { 1887 | var namespaces = _usings 1888 | .OrderBy(it => it == "System" || it.StartsWith("System.") ? 0 : 1) 1889 | .ThenBy(it => it) 1890 | .ToList(); 1891 | foreach (var ns in namespaces) 1892 | { 1893 | WriteNextLine("using ", ns, ";"); 1894 | } 1895 | WriteLine(); 1896 | } 1897 | 1898 | // NOTE: type alias cannot solve all name conflicted case, user should use PrintFullTypeName 1899 | // keep logic here for compatability 1900 | if (_typeNames != null) 1901 | { 1902 | var names = _typeNames 1903 | .Where(kvp => !kvp.Value.Contains('.') && GetTypeName(kvp.Key) != kvp.Value) 1904 | .OrderBy(kvp => kvp.Value) 1905 | .ToList(); 1906 | foreach (var name in names) 1907 | { 1908 | WriteNextLine("using ", name.Value, " = ", name.Key.FullName!, ";"); 1909 | } 1910 | if (names.Count > 0) 1911 | WriteLine(); 1912 | } 1913 | 1914 | if (Definitions.Namespace != null) 1915 | { 1916 | WriteNextLine("namespace ", Definitions.Namespace); 1917 | Indent(); 1918 | } 1919 | 1920 | var isInternal = Definitions.IsInternal; 1921 | if (!isInternal) 1922 | isInternal = Definitions.Implements?.Any(it => 1923 | !it.GetTypeInfo().IsInterface && !it.GetTypeInfo().IsPublic) ?? false; 1924 | WriteModifierNextLine(isInternal ? "internal" : "public"); 1925 | Write("partial ", Definitions.IsRecordType ? "record " : "class ", Definitions.TypeName); 1926 | if (Definitions.IsRecordType && ctorParams?.Count > 0) 1927 | { 1928 | WriteCtorParams(ctorParams); 1929 | } 1930 | if (implements?.Any() == true) 1931 | { 1932 | Write(" : ", string.Join(", ", implements)); 1933 | } 1934 | Indent(); 1935 | } 1936 | if (constants != null) 1937 | { 1938 | foreach (var constant in constants) 1939 | { 1940 | WriteModifierNextLine("private"); 1941 | Write(constant); 1942 | } 1943 | WriteLine(); 1944 | } 1945 | if (_properties != null && Definitions?.TypeName != null) 1946 | { 1947 | foreach (var property in _properties) 1948 | { 1949 | if (Definitions.IsRecordType && property.IsReadOnly) 1950 | continue; 1951 | var isInternal = property.Type.GetTypeInfo().IsNotPublic; 1952 | WriteModifierNextLine(isInternal ? "internal" : "public"); 1953 | Write(properties![property.Name]); 1954 | } 1955 | WriteLine(); 1956 | 1957 | if (ctorParams?.Count > 0 && !Definitions.IsRecordType) 1958 | { 1959 | var isInternal = ctorParams.Any(it => it.Type.GetTypeInfo().IsNotPublic); 1960 | WriteModifierNextLine(isInternal ? "internal" : "public"); 1961 | Write(Definitions.TypeName); 1962 | WriteCtorParams(ctorParams); 1963 | Indent(); 1964 | foreach (var parameter in ctorParams) 1965 | { 1966 | WriteNextLine("this.", parameter.Name, " = ", char.ToLower(parameter.Name[0]).ToString(), parameter.Name.Substring(1), ";"); 1967 | } 1968 | Outdent(); 1969 | WriteLine(); 1970 | } 1971 | } 1972 | 1973 | var sb = _writer.GetStringBuilder(); 1974 | if (temp.GetStringBuilder().Length > 0) 1975 | { 1976 | _writer.Write(temp); 1977 | if (_appendWriters != null) 1978 | { 1979 | foreach (var item in _appendWriters) 1980 | { 1981 | _writer.Write(item); 1982 | } 1983 | } 1984 | } 1985 | else 1986 | { 1987 | sb.Length = sb.FindEndIndex(); 1988 | } 1989 | 1990 | if (Definitions?.TypeName != null) 1991 | { 1992 | Outdent(); 1993 | if (Definitions?.Namespace != null) 1994 | Outdent(); 1995 | } 1996 | 1997 | int wsCount = sb.FindStartIndex(); 1998 | return sb.ToString(wsCount, sb.Length - wsCount); 1999 | } 2000 | finally 2001 | { 2002 | _writer = temp; 2003 | _indentLevel = oldIndent; 2004 | } 2005 | } 2006 | 2007 | private void WriteCtorParams(List ctorParams) 2008 | { 2009 | Write("("); 2010 | for (var i = 0; i < ctorParams.Count; i++) 2011 | { 2012 | var parameter = ctorParams[i]; 2013 | if (i > 0) 2014 | Write(", "); 2015 | Write($"{TranslateNullable(parameter.Type, parameter.NullableContext ?? Definitions?.NullableContext, parameter.Nullable)} {((Definitions?.IsRecordType ?? false) ? parameter.Name : char.ToLower(parameter.Name[0]) + parameter.Name.Substring(1))}"); 2016 | } 2017 | Write(")"); 2018 | } 2019 | 2020 | public enum LambdaType 2021 | { 2022 | PublicMethod, 2023 | PublicLambda, 2024 | PrivateMethod, 2025 | PrivateLambda, 2026 | ExtensionMethod, 2027 | } 2028 | } 2029 | } 2030 | -------------------------------------------------------------------------------- /ExpressionTranslator/ExpressionTranslator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1;netstandard2.0;netstandard1.3 5 | True 6 | Translate from linq expressions to C# code 7 | True 8 | true 9 | ExpressionTranslator.snk 10 | 2.5.0 11 | ExpressionDebugger 12 | enable 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ExpressionTranslator/ExpressionTranslator.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MapsterMapper/ExpressionDebugger/c4a153ea1bcfd9a02f234e2fb006423cb290c636/ExpressionTranslator/ExpressionTranslator.snk -------------------------------------------------------------------------------- /ExpressionTranslator/ExpressionTranslatorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace ExpressionDebugger 5 | { 6 | public static class ExpressionTranslatorExtensions 7 | { 8 | /// 9 | /// Generate script text 10 | /// 11 | public static string ToScript(this Expression node, ExpressionDefinitions? definitions = null) 12 | { 13 | var translator = ExpressionTranslator.Create(node, definitions); 14 | return translator.ToString(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ExpressionTranslator/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace ExpressionDebugger 8 | { 9 | internal static class Extensions 10 | { 11 | public static HashSet ToHashSet(this IEnumerable source) 12 | { 13 | return new HashSet(source); 14 | } 15 | 16 | #if NET40 17 | public static Type GetTypeInfo(this Type type) { 18 | return type; 19 | } 20 | #endif 21 | 22 | #if NET40 || NETSTANDARD1_3 23 | public static T GetCustomAttribute(this MemberInfo memberInfo) where T : Attribute 24 | { 25 | return (T)memberInfo.GetCustomAttributes(typeof(T), true).SingleOrDefault(); 26 | } 27 | 28 | public static T GetCustomAttribute(this Type type) where T : Attribute 29 | { 30 | return (T)type.GetTypeInfo().GetCustomAttributes(typeof(T), true).SingleOrDefault(); 31 | } 32 | #endif 33 | 34 | public static int FindStartIndex(this StringBuilder sb) 35 | { 36 | int wsCount = 0; 37 | for (int i = 0; i < sb.Length; i++) 38 | { 39 | if (char.IsWhiteSpace(sb[i])) 40 | wsCount++; 41 | else 42 | break; 43 | } 44 | return wsCount; 45 | } 46 | 47 | public static int FindEndIndex(this StringBuilder sb) 48 | { 49 | int wsCount = 0; 50 | for (int i = sb.Length - 1; i >= 0; i--) 51 | { 52 | if (char.IsWhiteSpace(sb[i])) 53 | wsCount++; 54 | else 55 | break; 56 | } 57 | return sb.Length - wsCount; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ExpressionTranslator/PropertyDefinitions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace ExpressionDebugger 6 | { 7 | public class PropertyDefinitions 8 | { 9 | public Type Type { get; set; } 10 | public string Name { get; set; } 11 | public bool IsReadOnly { get; set; } 12 | public bool IsInitOnly { get; set; } 13 | 14 | /// 15 | /// Set to 2 to mark type as nullable 16 | /// 17 | public byte? NullableContext { get; set; } 18 | 19 | /// 20 | /// If type is generic, array or tuple, you can mark nullable for each type 21 | /// Set to 2 for nullable 22 | /// 23 | public byte[]? Nullable { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ExpressionTranslator/TypeDefinitions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExpressionDebugger 5 | { 6 | public class TypeDefinitions 7 | { 8 | public string? Namespace { get; set; } 9 | public string? TypeName { get; set; } 10 | public bool IsStatic { get; set; } 11 | public bool IsInternal { get; set; } 12 | public IEnumerable? Implements { get; set; } 13 | public bool PrintFullTypeName { get; set; } 14 | public bool IsRecordType { get; set; } 15 | 16 | /// 17 | /// Set to 2 to mark all properties as nullable 18 | /// 19 | public byte? NullableContext { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 chaowlert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pack.bat: -------------------------------------------------------------------------------- 1 | if not exist "Packages" (mkdir "Packages") else (del /F /Q "Packages\*") 2 | dotnet restore 3 | dotnet msbuild /t:build /p:Configuration=Release /p:GeneratePackageOnBuild=false /p:ExcludeGeneratedDebugSymbol=false 4 | dotnet pack -c Release -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg -o Packages -------------------------------------------------------------------------------- /Publish.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set /p apikey="Enter Nuget.org API key: " 3 | for /r %%v in (Packages\*.nupkg) do nuget push %%v -ApiKey %apikey% -Source nuget.org -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Icon](https://cloud.githubusercontent.com/assets/5763993/26522656/41e28a6e-432f-11e7-9cae-7856f927d1a1.png) 2 | 3 | # ExpressionTranslator 4 | Translate from linq expressions to C# code 5 | 6 | ### Get it 7 | ``` 8 | PM> Install-Package ExpressionTranslator 9 | ``` 10 | 11 | ### Get readable script 12 | You can compile expression into readable script by `ToScript` extension method 13 | ```CSharp 14 | var script = lambda.ToScript(); 15 | ``` 16 | 17 | # ExpressionDebugger 18 | Step into debugging and generate readable script from linq expressions 19 | 20 | ### Get it 21 | ``` 22 | PM> Install-Package ExpressionDebugger 23 | ``` 24 | 25 | ### Compile with debug info 26 | `CompileWithDebugInfo` extension method will allow step-into debugging. 27 | ```CSharp 28 | var func = lambda.CompileWithDebugInfo(); 29 | func(); //<-- you can step-into this function!! 30 | ``` 31 | 32 | ### Version 2.0 .NET Core support! 33 | - Version 2.0 now support .NET Core 34 | 35 | ### Visual Studio for Mac 36 | To step-into debugging, you might need to emit file 37 | ```CSharp 38 | var opt = new ExpressionCompilationOptions { EmitFile = true }; 39 | var func = lambda.CompileWithDebugInfo(opt); 40 | func(); //<-- you can step-into this function!! 41 | ``` 42 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MapsterMapper/ExpressionDebugger/c4a153ea1bcfd9a02f234e2fb006423cb290c636/icon.png --------------------------------------------------------------------------------