Skip to content

Instantly share code, notes, and snippets.

@sailro
Created November 2, 2018 21:45
Show Gist options
  • Save sailro/1489cb08bac2b74570eccf013a1bb84c to your computer and use it in GitHub Desktop.
Save sailro/1489cb08bac2b74570eccf013a1bb84c to your computer and use it in GitHub Desktop.
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
namespace SyntaxTree.VisualStudio.Unity.Debugger.Evaluation
{
internal static class DebugSyntaxFactory
{
// Handle pseudo-variable like $exception or object-ids $1..., that are not parsed with the regular grammar
private static CSharpSyntaxNode Parse(string code, string parseMethodName)
{
try
{
if (!code.Contains("$"))
return null;
var source = SourceText.From(code);
var csSyntaxTypeFormat = $"Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax.{{0}}, {typeof(CSharpSyntaxNode).Assembly.FullName}";
var syntaxTypeFormat = $"Microsoft.CodeAnalysis.{{0}}, {typeof(Microsoft.CodeAnalysis.SyntaxTree).Assembly.FullName}";
// ReSharper disable PossibleNullReferenceException, AssignNullToNotNullAttribute
var lexerType = Type.GetType(string.Format(csSyntaxTypeFormat, "Lexer")) as TypeInfo;
var parserType = Type.GetType(string.Format(csSyntaxTypeFormat, "LanguageParser")) as TypeInfo;
// Create lexer, depending on Roslyn version, we have one extra argument (interpolationFollowedByColon)
var lexerCtor = lexerType.DeclaredConstructors.FirstOrDefault(c => c.GetParameters().Length >= 3);
var lexerCtorArgs = lexerCtor.GetParameters().Length == 3 ? new object[] {source, CSharpParseOptions.Default, false} : new object[] {source, CSharpParseOptions.Default, false, false};
var lexer = lexerCtor.Invoke(lexerCtorArgs) as IDisposable;
using (lexer)
{
var lexerModeType = Type.GetType(string.Format(csSyntaxTypeFormat, "LexerMode"));
var parser = parserType.DeclaredConstructors.First().Invoke(new[] {lexer, null, null, Convert.ChangeType(0x0002, Enum.GetUnderlyingType(lexerModeType)), default(CancellationToken)}) as IDisposable;
using (parser)
{
var parseExpressionMethod = parserType.GetDeclaredMethod(parseMethodName);
var node = parseExpressionMethod.Invoke(parser, new object[0]);
var consumeUnexpectedTokensMethod = parserType.GetDeclaredMethod("ConsumeUnexpectedTokens").MakeGenericMethod(node.GetType());
// node we have a correct node using the internal ast
node = consumeUnexpectedTokensMethod.Invoke(parser, new[] {node});
// convert to the public ast
var greenNodeType = Type.GetType(string.Format(syntaxTypeFormat, "GreenNode")) as TypeInfo;
var createRedMethod = greenNodeType.GetMethod("CreateRed", BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null);
var csNode = createRedMethod.Invoke(node, new object[0]);
var createForDebuggerMethod = typeof(CSharpSyntaxTree).GetMethod("CreateForDebugger", BindingFlags.NonPublic | BindingFlags.Static);
var syntaxTree = createForDebuggerMethod.Invoke(null, new[] {csNode, source}) as Microsoft.CodeAnalysis.SyntaxTree;
return syntaxTree.GetRoot() as CSharpSyntaxNode;
}
}
// ReSharper restore PossibleNullReferenceException, AssignNullToNotNullAttribute
}
catch (Exception)
{
return null;
}
}
public static CSharpSyntaxNode ParseExpression(string code)
{
return Parse(code, nameof(ParseExpression));
}
public static CSharpSyntaxNode ParseStatement(string code)
{
return Parse(code, nameof(ParseStatement));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment