Megosztás a következőn keresztül:


Bevezetés a szintaxiselemzés használatbavétele

Ebben az oktatóanyagban a Szintaxis API-t ismerheti meg. A Szintaxis API hozzáférést biztosít a C# vagy Visual Basic programot leíró adatstruktúrákhoz. Ezek az adatstruktúrák elég részletességgel rendelkeznek ahhoz, hogy bármilyen méretű programot teljes mértékben képviselni tudjanak. Ezek a struktúrák teljes programokat írnak le, amelyek megfelelően vannak lefordítva és futtatva. A szerkesztőben a hiányos programokat is leírhatják, ahogy ön írja őket.

Ennek a gazdag kifejezésnek az engedélyezéséhez a Szintaxis API-t alkotó adatstruktúrák és API-k szükségszerűen összetettek. Kezdjük a tipikus ""Helló világ!" alkalmazás" program adatstruktúrájával:

using System;
using System.Collections.Generic;
using System.Linq;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Nézze meg az előző program szövegét. Ismerős elemeket ismer fel. A teljes szöveg egyetlen forrásfájlt vagy fordítási egységet jelöl. A forrásfájl első három sora direktívákat használ. A fennmaradó forrás egy névtérdeklarációban található. A névtérdeklaráció tartalmaz egy gyermekosztály-deklarációt. Az osztálydeklaráció egy metódusdeklarációt tartalmaz.

A Szintaxis API létrehoz egy fastruktúrát a fordítási egységet képviselő gyökérrel. A fa csomópontjai a program összes többi elemét, a névtér-deklarációt és a használatban lévő irányelveket jelölik. A faszerkezet a legalacsonyabb szintekig folytatódik: a ""Helló világ!" alkalmazás!" sztring egy karakterlánckonstans jogkivonat, amely egy argumentum leszármazottja. A Szintaxis API hozzáférést biztosít a program szerkezetéhez. Lekérdezhet adott kódeljárásokat, végigvezetheti a teljes fát a kód megértéséhez, és új fákat hozhat létre a meglévő fa módosításával.

Ez a rövid leírás áttekintést nyújt a Szintaxis API használatával elérhető információk típusáról. A Szintaxis API nem más, mint egy formális API, amely leírja a C# nyelvből ismert ismert kódszerkezeteket. A teljes képesség tartalmazza a kód formázásának módját, beleértve a sortöréseket, a térközt és a behúzást. Ezen információk használatával teljes mértékben úgy ábrázolhatja a kódot, ahogyan azt az emberi programozók vagy a fordító írta és olvasta. Ennek a struktúranak a használatával mélyen értelmezhető szinten kezelheti a forráskódot. Már nem szöveges sztringek, hanem egy C#-program struktúráját képviselő adatok.

Az első lépésekhez telepítenie kell a .NET Compiler Platform SDK-t:

Telepítési utasítások – Visual Studio Installer

A .NET Compiler Platform SDK-t kétféleképpen keresheti meg a Visual Studio Installerben:

Telepítés a Visual Studio Telepítő – Számítási feladatok nézetével

A .NET Compiler Platform SDK nincs automatikusan kiválasztva a Visual Studio bővítményfejlesztési számítási feladatának részeként. Választható összetevőként kell kiválasztania.

  1. A Visual Studio Installer futtatása
  2. Válassza a Módosítás lehetőséget
  3. Ellenőrizze a Visual Studio bővítményfejlesztési számítási feladatát.
  4. Nyissa meg a Visual Studio bővítményfejlesztési csomópontját az összefoglaló fában.
  5. Jelölje be a .NET Fordítóplatform SDK jelölőnégyzetét. Az utolsót az opcionális összetevők alatt találja.

Igény szerint azt is szeretné, hogy a DGML-szerkesztő gráfokat jelenítsen meg a vizualizációban:

  1. Nyissa meg az Egyes összetevők csomópontot az összegző fában.
  2. Jelölje be a DGML-szerkesztő jelölőnégyzetét

Telepítés a Visual Studio Installer – Egyes összetevők lap használatával

  1. A Visual Studio Installer futtatása
  2. Válassza a Módosítás lehetőséget
  3. Válassza az Egyes összetevők lapot
  4. Jelölje be a .NET Fordítóplatform SDK jelölőnégyzetét. Ezt a Fordítók, buildelési eszközök és futtatókörnyezetek szakasz tetején találja.

Igény szerint azt is szeretné, hogy a DGML-szerkesztő gráfokat jelenítsen meg a vizualizációban:

  1. Jelölje be a DGML-szerkesztő jelölőnégyzetét. Ezt a Kódeszközök szakaszban találja.

A szintaxisfák ismertetése

A Szintaxis API-t a C#-kód szerkezetének elemzéséhez használhatja. A Szintaxis API elérhetővé teszi az elemzőket, a szintaxisfákat és a szintaxisfák elemzésére és létrehozására szolgáló segédprogramokat. Így kereshet adott szintaxiselemeket a kódban, vagy olvashatja egy program kódját.

A szintaxisfa olyan adatszerkezet, amelyet a C# és a Visual Basic fordítói használnak a C# és a Visual Basic programok megértéséhez. A szintaktikai fákat ugyanaz az elemző hozza létre, amely egy projekt létrehozásakor vagy egy fejlesztő F5-ös találatakor fut. A szintaxisfák teljes mértékben hűek a nyelvhez; a kódfájl minden egyes adatrészlete a fán jelenik meg. A szintaxisfa szövegbe írása reprodukálja az elemzett eredeti szöveget. A szintaxisfák szintén nem módosíthatók; a szintaktikai fa létrehozása után soha nem módosítható. A fák felhasználói több szálon elemezhetik a fákat zárolások vagy más egyidejűségi mértékek nélkül, tudva, hogy az adatok soha nem változnak. API-k használatával új fákat hozhat létre, amelyek egy meglévő fa módosításának eredményeként jönnek létre.

A szintaxisfák négy elsődleges építőeleme a következő:

A Trivia, a tokenek és a csomópontok hierarchikusan vannak összeállítva, hogy egy olyan fát alkotjanak, amely teljes egészében a Visual Basic- vagy C#-kódrészletek összes elemét képviseli. Ezt a struktúrát a Szintaxisábrázoló ablakban tekintheti meg. A Visual Studióban válassza azEgyéb Windows>szintaxisábrázolómegtekintése> lehetőséget. Például a szintaxisábrázolóval megvizsgált előző C#-forrásfájl az alábbi ábrához hasonlóan néz ki:

Szintaxiscsomópont: Kék | SzintaxisToken: Zöld | SyntaxTrivia: Red C# kódfájl

A fastruktúrában való navigálással bármilyen utasítást, kifejezést, jogkivonatot vagy kis üres területet megtalálhat egy kódfájlban.

Bár a Szintaxis API-k használatával bármit megtalálhat egy kódfájlban, a legtöbb forgatókönyvben kis kódrészletek vizsgálata vagy adott utasítások vagy töredékek keresése történik. Az alábbi két példa a kód szerkezetének böngészésére vagy az egyutas utasítások keresésére szolgáló tipikus felhasználási módokat mutatja be.

Fák bejárása

A szintaxisfán lévő csomópontokat kétféleképpen vizsgálhatja meg. A fa bejárásával megvizsgálhatja az egyes csomópontokat, vagy adott elemeket vagy csomópontokat kérdezhet le.

Manuális bejárás

A minta kész kódját a GitHub-adattárban tekintheti meg.

Megjegyzés

A Szintaxisfa típusok öröklődés használatával írják le a program különböző helyeinél érvényes szintaxiselemeket. Ezeknek az API-knak a használata gyakran azt jelenti, hogy tulajdonságokat vagy gyűjteménytagokat adott származtatott típusokra kell kiosztani. Az alábbi példákban a hozzárendelés és a leadások külön utasítások, explicit módon beírt változók használatával. A kódot elolvasva megtekintheti az API visszatérési típusait és a visszaadott objektumok futtatókörnyezeti típusát. A gyakorlatban gyakoribb, hogy implicit módon beírt változókat használunk, és API-nevekre támaszkodunk a vizsgált objektumok típusának leírásához.

Hozzon létre egy új C# különálló kódelemző eszközt :

  • A Visual Studióban válassza azÚj>projektfájlja> lehetőséget az Új projekt párbeszédpanel megjelenítéséhez.
  • A Visual C#>Bővíthetőség területen válassza a Különálló kódelemzési eszköz lehetőséget.
  • Adja a projektnek a "SzintaxisTreeManualTraversal" nevet, majd kattintson az OK gombra.

A korábban bemutatott alapszintű ""Helló világ!" alkalmazás!" programot fogja elemezni. Adja hozzá a "Helló világ!" alkalmazás program szövegét állandóként az Program osztályban:

        const string programText =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(""Hello, World!"");
        }
    }
}";

Ezután adja hozzá a következő kódot, hogy létrehozza a szintaxisfát a kódszöveghez az programText állandóban. Adja hozzá a következő sort a Main metódushoz:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Ez a két sor hozza létre a fát, és lekéri a fa gyökércsomópontját. Most már megvizsgálhatja a fában lévő csomópontokat. Adja hozzá ezeket a sorokat a Main metódushoz a gyökércsomópont néhány tulajdonságának megjelenítéséhez a fában:

WriteLine($"The tree is a {root.Kind()} node.");
WriteLine($"The tree has {root.Members.Count} elements in it.");
WriteLine($"The tree has {root.Usings.Count} using statements. They are:");
foreach (UsingDirectiveSyntax element in root.Usings)
    WriteLine($"\t{element.Name}");

Futtassa az alkalmazást annak megtekintéséhez, hogy a kód mit észlelt a fa gyökércsomópontjáról.

Általában a fán haladva ismerkedhet meg a kóddal. Ebben a példában az API-k megismeréséhez ismert kódot fog elemezni. Adja hozzá a következő kódot a csomópont első tagjának vizsgálatához root :

MemberDeclarationSyntax firstMember = root.Members[0];
WriteLine($"The first member is a {firstMember.Kind()}.");
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;

Ez a tag egy Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax. A deklaráció hatókörében namespace HelloWorld mindent képvisel. Adja hozzá a következő kódot a névtérben deklarált csomópontok vizsgálatához HelloWorld :

WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared in this namespace.");
WriteLine($"The first member is a {helloWorldDeclaration.Members[0].Kind()}.");

Futtassa a programot a tanultak megtekintéséhez.

Most, hogy már tudja, hogy a deklaráció egy Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax, deklaráljon egy ilyen típusú új változót az osztálydeklaráció vizsgálatához. Ez az osztály csak egy tagot tartalmaz: a metódust Main . Adja hozzá a következő kódot a Main metódus megkereséséhez, és adja hozzá egy Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntaxparancshoz.

var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0];
WriteLine($"There are {programDeclaration.Members.Count} members declared in the {programDeclaration.Identifier} class.");
WriteLine($"The first member is a {programDeclaration.Members[0].Kind()}.");
var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0];

A metódusdeklarációs csomópont tartalmazza a metódussal kapcsolatos összes szintaktikai információt. Jelenítse meg a Main metódus visszatérési típusát, az argumentumok számát és típusait, valamint a metódus törzsszövegét. Adja hozzá a következő kódot:

WriteLine($"The return type of the {mainDeclaration.Identifier} method is {mainDeclaration.ReturnType}.");
WriteLine($"The method has {mainDeclaration.ParameterList.Parameters.Count} parameters.");
foreach (ParameterSyntax item in mainDeclaration.ParameterList.Parameters)
    WriteLine($"The type of the {item.Identifier} parameter is {item.Type}.");
WriteLine($"The body text of the {mainDeclaration.Identifier} method follows:");
WriteLine(mainDeclaration.Body?.ToFullString());

var argsParameter = mainDeclaration.ParameterList.Parameters[0];

Futtassa a programot a programról felderített összes információ megtekintéséhez:

The tree is a CompilationUnit node.
The tree has 1 elements in it.
The tree has 4 using statements. They are:
        System
        System.Collections
        System.Linq
        System.Text
The first member is a NamespaceDeclaration.
There are 1 members declared in this namespace.
The first member is a ClassDeclaration.
There are 1 members declared in the Program class.
The first member is a MethodDeclaration.
The return type of the Main method is void.
The method has 1 parameters.
The type of the args parameter is string[].
The body text of the Main method follows:
        {
            Console.WriteLine("Hello, World!");
        }

Lekérdezési módszerek

A fák bejárása mellett a szintaxisfát is megismerheti a következő helyen Microsoft.CodeAnalysis.SyntaxNodedefiniált lekérdezési módszerekkel: . Ezeknek a módszereknek azonnal ismerősnek kell lenniük az XPath-t ismerők számára. Ezekkel a módszerekkel a LINQ-val gyorsan megtalálhatja a fában lévő dolgokat. A SyntaxNode lekérdezési metódusok, például DescendantNodes, AncestorsAndSelf és ChildNodes.

Ezekkel a lekérdezési metódusokkal megkeresheti a metódus argumentumát a Main fa navigálásának alternatívájaként. Adja hozzá a következő kódot a metódus aljára Main :

var firstParameters = from methodDeclaration in root.DescendantNodes()
                                        .OfType<MethodDeclarationSyntax>()
                      where methodDeclaration.Identifier.ValueText == "Main"
                      select methodDeclaration.ParameterList.Parameters.First();

var argsParameter2 = firstParameters.Single();

WriteLine(argsParameter == argsParameter2);

Az első utasítás egy LINQ-kifejezést és egy metódust használ az DescendantNodes előző példában szereplő paraméter megkereséséhez.

Futtassa a programot, és láthatja, hogy a LINQ kifejezés ugyanazt a paramétert találta, mint a fa manuális navigálása.

A minta WriteLine utasításokkal jeleníti meg a szintaxisfákkal kapcsolatos információkat a bejárásuk során. A kész programot a hibakereső alatt futtatva is sokkal többet tudhat meg. A hello world programhoz létrehozott szintaxisfa további tulajdonságait és metódusait is megvizsgálhatja.

Szintaktikai lépegetők

Gyakran előfordul, hogy egy szintaxisfában egy adott típus összes csomópontját meg szeretné találni, például egy fájl minden tulajdonságdeklarációját. Az osztály kibővítésével Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker és a VisitPropertyDeclaration(PropertyDeclarationSyntax) metódus felülbírálásával minden tulajdonságdeklarációt egy szintaxisfában dolgoz fel anélkül, hogy előzetesen ismerné annak szerkezetét. CSharpSyntaxWalker egy adott típusú CSharpSyntaxVisitor , amely rekurzívan meglátogat egy csomópontot és annak minden gyermekét.

Ez a példa egy CSharpSyntaxWalker szintaxisfát vizsgál meg. Olyan irányelveket using gyűjt, amelyek nem importálnak névteret System .

Hozzon létre egy új C# különálló kódelemző eszközprojektet ; adja neki a "SyntaxWalker" nevet.

A minta kész kódját a GitHub-adattárban tekintheti meg. A GitHubon található minta az oktatóanyagban ismertetett mindkét projektet tartalmazza.

Az előző példához hasonlóan definiálhat egy sztringkonstanst az elemezni kívánt program szövegének tárolásához:

        const string programText =
@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace TopLevel
{
    using Microsoft;
    using System.ComponentModel;

    namespace Child1
    {
        using Microsoft.Win32;
        using System.Runtime.InteropServices;

        class Foo { }
    }

    namespace Child2
    {
        using System.CodeDom;
        using Microsoft.CSharp;

        class Bar { }
    }
}";

Ez a forrásszöveg négy különböző helyen szétszórt irányelveket tartalmaz using : a fájlszintet, a legfelső szintű névteret és a két beágyazott névteret. Ez a példa egy alapvető forgatókönyvet mutat be a CSharpSyntaxWalker osztály kód lekérdezéséhez. Nehézkes lenne a gyökérszintaxisfa minden csomópontját felkeresni, hogy deklarációkat használjon. Ehelyett létrehoz egy származtatott osztályt, és felülbírálja azt a metódust, amelyet csak akkor hív meg a rendszer, ha a fa aktuális csomópontja egy használati irányelv. Látogatója semmilyen más csomóponttípuson nem végez munkát. Ez az egyetlen metódus megvizsgálja az using egyes utasításokat, és létrehoz egy gyűjteményt a névtérben nem szereplő System névterekből. Létrehozhat egy olyant CSharpSyntaxWalker , amely az összes utasítást using megvizsgálja, de csak az using utasításokat.

Most, hogy definiálta a program szövegét, létre kell hoznia egy SyntaxTree , majd le kell kérnie a fa gyökerét:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Ezután hozzon létre egy új osztályt. A Visual Studióban válassza a ProjectAdd New Item (Új elem hozzáadása) lehetőséget>. Az Új elem hozzáadása párbeszédpanelen írja be a UsingCollector.cs nevet fájlnévként.

Megvalósítja a using látogatói funkciókat az UsingCollector osztályban. Első lépésként a osztályt a UsingCollector következőből kell származtatni CSharpSyntaxWalker: .

class UsingCollector : CSharpSyntaxWalker

A gyűjtött névtércsomópontok tárolásához tárolóra van szükség. Deklaráljon egy nyilvános írásvédett tulajdonságot a UsingCollector osztályban. Ezzel a változóval tárolhatja a UsingDirectiveSyntax talált csomópontokat:

public ICollection<UsingDirectiveSyntax> Usings { get; } = new List<UsingDirectiveSyntax>();

Az alaposztály CSharpSyntaxWalker implementálja a szintaxisfa egyes csomópontjaihoz tartozó logikát. A származtatott osztály felülbírálja az önt érdeklő csomópontok metódusait. Ebben az esetben minden using irányelv érdekel. Ez azt jelenti, hogy felül kell bírálnia a metódust VisitUsingDirective(UsingDirectiveSyntax) . A metódus egyetlen argumentuma egy Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax objektum. Ez egy fontos előnye a látogatók használatának: a felülírt metódusokat úgy hívják, hogy az argumentumok már az adott csomóponttípusra vannak adva. Az Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax osztály rendelkezik egy Name tulajdonságtal, amely az importált névtér nevét tárolja. Ez egy Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Adja hozzá a következő kódot a felülbíráláshoz VisitUsingDirective(UsingDirectiveSyntax) :

public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
    WriteLine($"\tVisitUsingDirective called with {node.Name}.");
    if (node.Name.ToString() != "System" &&
        !node.Name.ToString().StartsWith("System."))
    {
        WriteLine($"\t\tSuccess. Adding {node.Name}.");
        this.Usings.Add(node);
    }
}

A korábbi példához hasonlóan számos WriteLine utasítással segítette a módszer megértését. Láthatja, hogy mikor van meghívva, és milyen argumentumokat ad át neki minden alkalommal.

Végül két sornyi kódot kell hozzáadnia a UsingCollector létrehozásához, és meg kell keresnie a gyökércsomópontot, összegyűjtve az összes utasítást using . Ezután adjon hozzá egy hurkot foreach a using gyűjtő által talált összes utasítás megjelenítéséhez:

var collector = new UsingCollector();
collector.Visit(root);
foreach (var directive in collector.Usings)
{
    WriteLine(directive.Name);
}

Fordítsa le és futtassa a programot. A következő kimenetnek kell megjelennie:

        VisitUsingDirective called with System.
        VisitUsingDirective called with System.Collections.Generic.
        VisitUsingDirective called with System.Linq.
        VisitUsingDirective called with System.Text.
        VisitUsingDirective called with Microsoft.CodeAnalysis.
                Success. Adding Microsoft.CodeAnalysis.
        VisitUsingDirective called with Microsoft.CodeAnalysis.CSharp.
                Success. Adding Microsoft.CodeAnalysis.CSharp.
        VisitUsingDirective called with Microsoft.
                Success. Adding Microsoft.
        VisitUsingDirective called with System.ComponentModel.
        VisitUsingDirective called with Microsoft.Win32.
                Success. Adding Microsoft.Win32.
        VisitUsingDirective called with System.Runtime.InteropServices.
        VisitUsingDirective called with System.CodeDom.
        VisitUsingDirective called with Microsoft.CSharp.
                Success. Adding Microsoft.CSharp.
Microsoft.CodeAnalysis
Microsoft.CodeAnalysis.CSharp
Microsoft
Microsoft.Win32
Microsoft.CSharp
Press any key to continue . . .

Gratulálunk! A Szintaxis API-val meghatározott C#-utasításokat és -deklarációkat talált a C#-forráskódban.