2014-07-17 5 views
9

Ich muss Fragmente von benutzerdefinierten C# -Code analysieren und ersetzen alle Variablen, die nicht lokal mit Methodenaufrufen definiert sind. I.e.Ersetzen Sie alle Variablen in C# -Code mit Methoden

public class Foo 
{ 
    public dynamic Bar() 
    { 
    return Math.Min(x + width, maxWidth); 
    } 
} 

hat werden:

public class Foo 
{ 
    public dynamic Bar() 
    { 
     return Math.Min(Resolve("x") + Resolve("width"), Resolve("maxWidth")); 
    } 
} 

Ich Microsoft.CodeAnalysis.CSharp und die CSharpSyntaxTree mit dem String zu prüfen, aber es gibt mir nicht genug Informationen, die ersetzen auszuführen. Oder wenn es so ist, weiß ich nicht, wo ich danach suchen soll. Ich habe das SyntaxTree-Layout unten eingefügt. Alle Variablen treten als IdentifierName-Knoten auf, aber ich weiß nicht, wie ich verschiedene IdentifierNames unterscheiden kann. Wohin geht es von hier?

CompilationUnit[0..99) { 
code: public class Foo\n{\n public dynamic Bar()\n {\n return Math.Min(x + width, maxWidth);\n }\n} 
tokens: EndOfFileToken[] 
nodes{ 
    ClassDeclaration[0..99) { 
    code: public class Foo\n{\n public dynamic Bar()\n {\n return Math.Min(x + width, maxWidth);\n }\n} 
    tokens: PublicKeyword[public ] ClassKeyword[class ] IdentifierToken[Foo\n] OpenBraceToken[{\n] CloseBraceToken[}] 
    nodes{ 
    MethodDeclaration[21..98) { 
    code: public dynamic Bar()\n {\n return Math.Min(x + width, maxWidth);\n }\n 
    tokens: PublicKeyword[ public ] IdentifierToken[Bar] 
    nodes{ 
     IdentifierName[30..38) { 
     code: dynamic 
     tokens: IdentifierToken[dynamic ] 
     } 
     ParameterList[41..45) { 
     code: ()\n 
     tokens: OpenParenToken[(] CloseParenToken[)\n] 
     } 
     Block[45..98) { 
     code: {\n return Math.Min(x + width, maxWidth);\n }\n 
     tokens: OpenBraceToken[ {\n] CloseBraceToken[ }\n] 
     nodes{ 
     ReturnStatement[50..93) { 
     code:  return Math.Min(x + width, maxWidth);\n 
     tokens: ReturnKeyword[ return ] SemicolonToken[;\n] 
     nodes{ 
      InvocationExpression[61..90) { 
      code: Math.Min(x + width, maxWidth) 
      nodes{ 
      SimpleMemberAccessExpression[61..69) { 
      code: Math.Min 
      tokens: DotToken[.] 
      nodes{ 
       IdentifierName[61..65) { 
       code: Math 
       tokens: IdentifierToken[Math] 
       } 
       IdentifierName[66..69) { 
       code: Min 
       tokens: IdentifierToken[Min] 
       } 
      } 
      } 
      ArgumentList[69..90) { 
      code: (x + width, maxWidth) 
      tokens: OpenParenToken[(] CommaToken[, ] CloseParenToken[)] 
      nodes{ 
       Argument[70..79) { 
       code: x + width 
       nodes{ 
       AddExpression[70..79) { 
       code: x + width 
       tokens: PlusToken[+ ] 
       nodes{ 
        IdentifierName[70..72) { 
        code: x 
        tokens: IdentifierToken[x ] 
        } 
        IdentifierName[74..79) { 
        code: width 
        tokens: IdentifierToken[width] 
        } 
       } 
       } 
       } 
       } 
       Argument[81..89) { 
       code: maxWidth 
       nodes{ 
       IdentifierName[81..89) { 
       code: maxWidth 
       tokens: IdentifierToken[maxWidth] 
       } 
       } 
       } 
      } 
      } 
      } 
      } 
     } 
     } 
     } 
     } 
    } 
    } 
    } 
    } 
} 
} 
+1

Der Syntaxbaum ist nicht genug, Sie müssen das semantische Modell betrachten, um zu sehen, was die Bezeichner darstellen. –

+0

Können Sie mir einen Link geben, wo ich semantische Modelle lesen kann? –

+0

Jetzt suchen ... Ich habe Roslyn in einer Weile nicht angeschaut, und die API hat sich seit dem letzten Mal sehr verändert –

Antwort

7

Ich denke, Sie müssen das semantische Modell verwenden. Hier ist ein (sehr einfaches) Beispiel, das zeigt, wie ungelöste Symbole zu finden:

var tree = CSharpSyntaxTree.ParseFile(fileName); 
var root = tree.GetRoot(); 
var refs = new MetadataReference[] 
{ 
    new MetadataFileReference(@"C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll", new MetadataReferenceProperties(MetadataImageKind.Assembly)) 
}; 
var compilation = CSharpCompilation.Create("testRoslyn", new[] { tree }, refs); 
var model = compilation.GetSemanticModel(tree); 

var unknownSymbols = 
    from node in root.DescendantNodes() 
    where node.IsKind(SyntaxKind.IdentifierName) 
    let symbolInfo = model.GetSymbolInfo(node) 
    where symbolInfo.Symbol == null && !symbolInfo.CandidateSymbols.Any() 
    select node; 

Von dort können Sie die Knoten mit Resolve(name) ersetzen können.

+0

Nun, das alles funktioniert ganz gut, ich bin immer noch total darauf, wie man die Knoten ersetzen kann. Es gibt etwas über die Logik hinter Microsoft.CodeAnalysis, die ich einfach noch nicht verstehe ... –

+1

Haben Sie die FAQ schon gelesen? Sie diskutieren über den Ersatz von Unterausdrücken dort: https://roslyn.codeplex.com/wikipage?title=FAQ&referringTitle=Documentation – JoshVarty

+0

Ich kann nicht die SDK-Vorschau herunterladen, die die Beispiele enthalten soll. Der Link führt mich stattdessen zu https://connect.microsoft.com/VisualStudio und von dort ist alles bergab. Ich habe meinen Computer nach faq.cs und allem anderen, was mir einfällt, durchsucht, aber keine Freude. –

1

Vielleicht fügen Sie versehentlich CSharpSyntaxTree des Codes ohne Deklaration "double width = 10.0;" Wenn ja, erhalten Sie diese zusätzlichen Deklarationen in Ihrem CSharpSyntaxTree.

Alles, was Sie tun müssen, ist nur den Baum nach IdentifierToken zu scannen, die nicht im Benutzercode deklariert sind. All diese Token haben Positionen, die Sie zum Ersetzen von Variablen Access Code zu Methodenaufrufcode verwenden müssen.

+0

Ich habe fälschlicherweise den Code später geändert, um zu zeigen beide definierten eine undefinierte Variable und vergaßen dabei, dass der Codebaum dann ungültig würde. Ich habe es geändert. –

2

Dieser Beitrag bietet nicht wirklich eine Lösung für Ihr Problem, sondern stattdessen eine andere Möglichkeit, die einfacher zu implementieren ist, aber eine Änderung für den Benutzer einführen. Ich poste hier nur als eine Idee.

Ziel ist es nicht, damit der Benutzer x schreiben, sondern Var.x oder Var.maxWidth

Dann, wenn Sie Ihre C# -Code Parsen, müssen Sie nur Code einfügen für eine Eigenschaft Var vom Typ CustomDynamicObject (oder einem beliebigen Namen geben will)

public (static?) CustomDynamicObject Var { get { /* create the object once and return */ }} 

Dann können Sie definieren DynamicObjectCustomDynamicObject vererben, so dass Sie alle Anrufe zu undefinierten Methoden/Eigentum abfangen

DynamicObject und Verwendung der dynamischen Funktion von .NET 4 ist nur eine Möglichkeit, Anruf abzufangen, aber Sie können für andere Techniken googlen.

+0

Ich würde eher keine Verantwortung für den Benutzer, wenn ich es helfen kann. Es ist eine interessante Idee, aber ich möchte sie zunächst lösen, ohne dass der Benutzercode sich vom normalen C# -Code unterscheidet. –

+0

Ja, deshalb poste ich es "als Idee". Wenn Sie keine Lösung für Ihr Problem finden, kann es sich um eine Backup-Lösung handeln. Aber Sie haben völlig Recht, es muss den Benutzer so wenig wie möglich belasten. – Fabske