SyntaxTreeSample/DebuggerConsole
using System; | |
using System.CodeDom.Compiler; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Threading; | |
using DebuggerLib; | |
namespace DebuggerConsole | |
{ | |
class Program | |
{ | |
const string SourcePath = @"..\..\..\NumericConsole\Program.cs"; | |
const string GeneratedPath = @"Program.g.cs"; | |
static void Main(string[] args) | |
{ | |
// Generates the code for debugging. | |
var sourceCode = File.ReadAllText(SourcePath); | |
var generatedCode = SyntaxHelper.InsertBreakpoints(sourceCode); | |
File.WriteAllText(GeneratedPath, generatedCode, Encoding.UTF8); | |
// Compiles and loads the assembly. | |
var provider = CodeDomProvider.CreateProvider("CSharp"); | |
var compilerOption = new CompilerParameters(new[] { "System.Core.dll", "DebuggerLib.dll" }) { GenerateExecutable = true }; | |
var compilerResult = provider.CompileAssemblyFromFile(compilerOption, GeneratedPath); | |
if (compilerResult.Errors.HasErrors) return; | |
// Registers the action for breakpoints. | |
DebugHelper.InfoNotified += (spanStart, spanLength, variables) => | |
{ | |
Console.WriteLine(string.Join(", ", variables.Select(v => $"{v.Name}: {v.Value}"))); | |
Console.WriteLine(sourceCode.Substring(spanStart, spanLength)); | |
Thread.Sleep(1000); | |
}; | |
// Calls the Main method. | |
var entryPoint = compilerResult.CompiledAssembly.EntryPoint; | |
entryPoint.Invoke(null, new object[] { new string[0] }); | |
} | |
} | |
} |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.CSharp; | |
using Microsoft.CodeAnalysis.CSharp.Syntax; | |
using Microsoft.CodeAnalysis.Text; | |
using DebugStatement = System.ValueTuple<Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax, string[]>; | |
namespace DebuggerConsole | |
{ | |
public static class SyntaxHelper | |
{ | |
public static string InsertBreakpoints(string sourceCode) | |
{ | |
var root = ParseText(sourceCode); | |
var method = root.DescendantNodes() | |
.OfType<MethodDeclarationSyntax>() | |
.FirstOrDefault(m => m.Identifier.ValueText == "Main") | |
?? throw new FormatException("The Main method is not found."); | |
var statements = DetectStatements(method); | |
var result = sourceCode; | |
foreach (var (statement, variables) in statements.Reverse()) | |
{ | |
var (span, debugIndex) = GetSpan(statement); | |
result = result.Insert(debugIndex, $"DebugHelper.NotifyInfo({span.Start}, {span.Length}{ToParamsArrayText(variables)});\r\n"); | |
} | |
return result.Insert(root.Usings.FullSpan.End, "using DebuggerLib;\r\n"); | |
} | |
public static CompilationUnitSyntax ParseText(string text) | |
{ | |
var tree = CSharpSyntaxTree.ParseText(text); | |
var diagnostics = tree.GetDiagnostics().ToArray(); | |
if (diagnostics.Length > 0) throw new FormatException(diagnostics[0].ToString()); | |
return tree.GetCompilationUnitRoot(); | |
} | |
public static DebugStatement[] DetectStatements(MethodDeclarationSyntax method) | |
{ | |
var statements = new List<DebugStatement>(); | |
DetectStatements(method.Body, statements, new List<(string, SyntaxNode)>()); | |
return statements.ToArray(); | |
} | |
static void DetectStatements(SyntaxNode node, List<DebugStatement> statements, List<(string name, SyntaxNode scope)> variables) | |
{ | |
// Adds variables. | |
if (node is VariableDeclarationSyntax varSyntax) | |
{ | |
var varNames = varSyntax.Variables.Select(v => v.Identifier.ValueText).ToArray(); | |
var scope = ((node.Parent is LocalDeclarationStatementSyntax) ? node.Parent : node) | |
.Ancestors() | |
.First(n => n is StatementSyntax); | |
variables.AddRange(varNames.Select(v => (v, scope))); | |
} | |
// Maps variables to the statement. | |
if ((node is StatementSyntax statement) && | |
!(node is BlockSyntax) && | |
!(node is BreakStatementSyntax)) | |
statements.Add((statement, variables.Select(v => v.name).ToArray())); | |
// Recursively. | |
foreach (var child in node.ChildNodes()) | |
DetectStatements(child, statements, variables); | |
// Maps variables to the last line of the block. | |
if (node is BlockSyntax block) | |
statements.Add((block, variables.Select(v => v.name).ToArray())); | |
// Clears variables out of the scope. | |
if (node is StatementSyntax) | |
for (var i = variables.Count - 1; i >= 0; i--) | |
if (variables[i].scope == node) | |
variables.RemoveAt(i); | |
else | |
break; | |
} | |
static (TextSpan, int) GetSpan(StatementSyntax statement) | |
{ | |
switch (statement) | |
{ | |
case ForStatementSyntax f: | |
var span = new TextSpan(f.ForKeyword.Span.Start, f.CloseParenToken.Span.End - f.ForKeyword.Span.Start); | |
return (span, statement.FullSpan.Start); | |
case BlockSyntax b: | |
return (b.CloseBraceToken.Span, b.CloseBraceToken.FullSpan.Start); | |
default: | |
return (statement.Span, statement.FullSpan.Start); | |
} | |
} | |
static string ToParamsArrayText(string[] variables) => | |
string.Concat(variables.Select(v => $", new Var(\"{v}\", {v})")); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment