A szintaxiselemzés első lépései

Ebben az oktatóanyagban megismerheti a Szintaxis API-t. 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 leírják a helyesen lefordított és futtatott teljes programokat. 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 azzal, hogy az adatszerkezet hogyan néz ki a tipikus "Hello World" program esetében:

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

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

Tekintse 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át használ. A fennmaradó forrás egy névtérdeklarációban található. A névtér deklarációja gyermekosztály-deklarációt tartalmaz. 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 using program irányelveit, névtér-deklarációját és minden egyéb elemét jelölik. A faszerkezet egészen a legalacsonyabb szintekig folytatódik: a "Hello World!" sztring egy sztringkonstans token, amely egy argumentum leszármazottja. A Szintaxis API hozzáférést biztosít a program szerkezetéhez. Lekérdezhet bizonyos 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#-bó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 szabad területet és a behúzást. Ezen információk használatával teljes mértékben képviselheti a kódot az emberi programozók vagy a fordító által írt és olvasott kódként. Ennek a struktúrának 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.

Első lépésként telepítenie kell a .NET Fordítóplatform SDK-t:

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

A .NET Fordítóplatform SDK-t kétféleképpen keresheti meg a Visual Studio Installerben:

Telepítés a Visual Studio Installer – Munkaterhelések nézet használatával

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

  1. 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ópontot az összefoglaló fán.
  5. Győződjön meg arról, hogy a .NET Fordítóplatform SDK jelölőnégyzete be van jelölve.
  6. Válassza a Módosítás lehetőséget.

Szükség esetén azt is szeretné, hogy a DGML-szerkesztő diagramokat jelenítsen meg a vizualizációban:

  1. Nyissa meg az Egyes összetevők csomópontot az összefoglaló fán.
  2. A DGML-szerkesztő jelölőnégyzetének bejelölése

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

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

Szükség esetén azt is szeretné, hogy a DGML-szerkesztő diagramokat jelenítsen meg a vizualizációban:

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

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 kódot adott szintaxiselemekre, vagy hogyan olvashatja el egy program kódját.

A szintaxisfa a C# és a Visual Basic fordítói által a C# és a Visual Basic programok megértéséhez használt adatstruktúra. A szintaxisfá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 hűséggel rendelkeznek a nyelvvel; a kódfájlban lévő összes információ jelenik meg a fában. A szintaxisfa szövegre írása a pontos eredeti, elemezett szöveget reprodukálja. A szintaxisfák szintén nem módosíthatók; miután létrehozott egy szintaxisfát, soha nem módosítható. A fák felhasználói több szálon, zárolások és egyéb egyidejűségi mértékek nélkül elemezhetik a fákat, tudva, hogy az adatok nem változnak. Az API-k segítségével új fákat hozhat létre, amelyek egy meglévő fa módosításának eredményei.

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

A Trivia elemek, a tokenek és a csomópontok hierarchikusan vannak összeállítva, hogy olyan fát alkossanak, amely teljes mértékben képviseli a Visual Basic vagy a C# kódrészlet minden egyes elemét. Ezt a struktúrát a Szintaxisábrázoló ablakban tekintheti meg. A Visual Studióban válassza a Megtekintés>Egyéb ablakok>Szintaxisvizualizáló lehetőséget. A Szintaxisábrázolóval megvizsgált előző C#-forrásfájl például a következő ábrához hasonlóan néz ki:

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

A faszerkezet navigálásával bármilyen utasítást, kifejezést, jogkivonatot vagy kis üres területet talá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ában lévő csomópontokat kétféleképpen vizsgálhatja meg. Az egyes csomópontok vizsgálatához bejárást végezhet a fán, vagy lekérdezhet bizonyos elemeket vagy csomópontokat.

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és használatával írják le a program különböző pontjain érvényes különböző szintaxiselemeket. Ezeknek az API-knak a használata gyakran azt jelenti, hogy tulajdonságokat vagy gyűjteménytagokat ad meg adott származtatott típusok számára. 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# Stand-Alone Code Analysis Tool-projektet :

  • A Visual Studióban válassza azÚj> projekt fájlja>lehetőséget az Új projekt párbeszédpanel megjelenítéséhez.
  • A Visual C#>Bővíthetőség csoportban válassza Stand-Alone Kódelemző eszközt.
  • Nevezze el a projektet "SyntaxTreeManualTraversal" névvel, és kattintson az OK gombra.

A korábban bemutatott alapszintű "Hello World!" programot fogja elemezni. Adja hozzá a Hello World 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 a kód szövegének szintaxisfája létrehozásához 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 fa csomópontjait. 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 directives. They are:");
foreach (UsingDirectiveSyntax element in root.Usings)
    WriteLine($"\t{element.Name}");

Futtassa az alkalmazást, hogy megtekintse, mit fedezett fel a kód a fa gyökércsomópontjával kapcsolatban.

Á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 elemzi. 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. Ez a deklaráció hatókörében namespace HelloWorld lévő mindenre utal. 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, hogy megtalálja a Main metódust, és végezze el annak castolását egy Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax típusra.

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 metódus visszatérési Main 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 directives. 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 megadott Microsoft.CodeAnalysis.SyntaxNodelekérdezési módszerekkel. Ezeknek a módszereknek azonnal ismerősnek kell lenniük az XPath-t ismerők számára. Ezeket a módszereket a LINQ használatával gyorsan megtalálhatja a fában. A SyntaxNode lekérdezési metódusok, például a DescendantNodes, AncestorsAndSelf és ChildNodes.

Ezekkel a lekérdezési metódusokkal megkeresheti a Main metódus argumentumát, a 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 linq kifejezést és metódust DescendantNodes használ az előző példában szereplő paraméter megkereséséhez.

Futtassa a programot, és láthatja, hogy a LINQ kifejezés megtalálta ugyanazt a paramétert, mint a fán történő manuális navigálás.

A minta a WriteLine utasítások használatával 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 szintaxisfának több tulajdonságát és metódusát is megvizsgálhatja.

Szintaktikai lépegetők

Gyakran egy adott típusú csomópontot szeretne megkeresni egy szintaxisfában, 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 a szintaxisfában lévő összes tulajdonságdeklarációt anélkül dolgozhatja fel, hogy előzetesen ismerné annak szerkezetét. CSharpSyntaxWalker egy adott fajta CSharpSyntaxVisitor , amely rekurzívan látogat egy csomópontot és annak minden gyermekét.

Ez a példa megvalósít egy CSharpSyntaxWalker-t, amely vizsgál egy szintaxisfát. Összegyűjti azokat a using irányelveket, amelyeket talál, és amelyek nem importálnak System névteret.

Hozzon létre egy új C# Stand-Alone Code Analysis Tool-projektet ; a "SyntaxWalker" nevet adja neki.

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 sztringállandót 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 emel ki az CSharpSyntaxWalker osztály kód lekérdezéséhez. Nehézkes lenne a gyökér szintaxisfa minden csomópontját felkeresni, hogy használati deklarációkat találjon. Ehelyett létrehoz egy származtatott osztályt, és felülírja azt a metódust, amelyet csak akkor hív meg, ha a fa aktuális csomópontja egy using irányelv. A látogató nem végez semmilyen munkát más csomóponttípusokon. Ez az egyetlen módszer megvizsgálja az using irányelvek mindegyikét, és létrehozza azoknak a névtereknek a gyűjteményét, amelyek nem szerepelnek a(z) System névtérben. Összeállít egy CSharpSyntaxWalker-t, amely megvizsgálja az összes using irányelvet, de csak a using irányelveket.

Most, hogy definiálta a programszöveget, létre kell hoznia egy SyntaxTree, és meg kell szereznie 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>. A Új elem hozzáadása párbeszédpanelben fájlnévként UsingCollector.cs írja be.

Az using látogatói funkciót a UsingCollector osztályban valósítson meg. Kezdje azzal, hogy az UsingCollector osztályt a CSharpSyntaxWalker osztályból származtatja.

class UsingCollector : CSharpSyntaxWalker

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

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

Az alaposztály CSharpSyntaxWalker implementálja a logikát a szintaxisfa minden csomópontjának felkereséséhez. A származtatott osztály felülbírálja az önt érdeklő csomópontok metódusait. Ebben az esetben bármilyen using irányelv érdekli. Ez azt jelenti, hogy felül kell bírálnia a VisitUsingDirective(UsingDirectiveSyntax) metódust. A metódus egyetlen argumentuma egy Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax objektum. Ez fontos előnye a látogatók használatának: a felülírt metódusokat az adott csomóponttípusra már leadott argumentumokkal hívják meg. Az Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax osztálynak van egy Name tulajdonsága, 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 különféle WriteLine utasításokat adott hozzá, hogy segítse a módszer megértését. Láthatja, hogy mikor hívják meg, és milyen argumentumokat kap minden alkalommal.

Végül két sornyi kódot kell hozzáadnia a UsingCollector létrehozásához, és hogy meglátogassa a gyökércsomópontot, összegyűjtve az összes using irányelvet. Ezután adjon hozzá egy foreach hurkot az összes using irányelv megjelenítéséhez, amelyet a gyűjtője talált.

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

Állítsa össze é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álok! A Szintaxis API használatával meghatározott típusú irányelveket és deklarációkat talált a C#-forráskódban.