Syntaktické stromy jsou pravděpodobně nejdůležitější datovou strukturou spodní vrstvy Roslyn API, protože bez nich se žádný nástroj a ani samotný kompilátor nikam nepohnou. Proto se na ně podíváme opravdu zblízka. Vysvětlíme si, z jakých stavebních prvků se skládají a jaké mají důležité vlastnosti.
Následuje seznam všech předcházejících článků série, které doporučuji přečíst, než se vrhnete na tento.
- Seznamte se s Roslynem
- Roslyn - pracovní prostředí
compiler api
Opět připomenu, že se jedná o následující vrstvu, jejíž pravděpodobně nejzajímavější částí je pro nás syntaktická a sémantická reprezentace zdrojového kódu.
I tato vrstva otevírá nové možnosti, i když v tomto případě je potencionálních konzumentů mnohem méně, než v případě vrstvy workspaces. Cituji v překladu:
“Výhody Roslynu osobně vidím ve třech kruzích – desítky, tisíce a milióny uživatelů.
Pro desítky – to jsme my, C# a Visual Basic design tým – malé novinky v jazyce budou mít konečně i malou cenu implementace díky dobře navrhnuté architektuře.
Pro tisíce – interní i externí partneři – to znamená otevřený přístup k interním informacím například při vývoji nástrojů do Visual Studia. Jako dobrý příklad mě napadají Code Lens z VS 2013, které byly postaveny na starší verzi Roslynu.
Pro milióny – všichni budeme těžit z velké řady nástrojů a celkové user experience (pojem vysvětlen zde).” (D. Campbell, .NET Compiler Platform (“Roslyn”) for the Rest of Us)
Syntaktický strom
Syntaktické stromy (dále označovány jako AST) jsou primární datová struktura při kompilaci, analýze kódu, sémantické analýze a žádná část zdrojového kódu není z pohledu nástrojů rozpoznána, dokud není k dispozici AST reprezentovaný strukturou předdefinovaných elementů. AST mají tři hlavní vlastnosti:
- Udržují si informace o všem, co je ve zdrojovém textu, včetně bílých znaků, komentářů a dokonce i chyb ve zdrojovém kódu, který je nekompletní.
- Díky první vlastnosti je možné AST převést zpět na text v téže podobě, z jaké byl rozparsován. To umožňuje provádět rozsáhlé změny ve struktuře souboru a stále zachovat formát, který si uživatel definoval.
- AST je neměnný, jakmile je jednou postavený. To nabízí spoustu výhod s touto vlastností spojenou, ale také otázku ohledně konzumace paměti. AST snapshoty vytvořené na základě jiných recyklují dříve vyrobené uzly stromu a proces je tak rychlý a paměťově nenáročný.
AST je postaven ze tří druhů elementů (jejichž označení nebudu překládat) – node, token, trivia. Aby byl následující popis jednotlivých elementů lépe uchopitelný, vytvořil jsem malý příklad, na který se budu celou dobu odkazovat a vše na něm vysvětlovat. Nemusíte se tedy trápit, jestli mu rovnou nerozumíte, prostě pokračujte v čtení a vracejte se k příkladu, kdykoliv se na něj odkážu.
int a = 1 + 2;
Pro tento kousek kódu Roslyn vygeneruje následující AST. Na jediný řádek kódu je to poměrně obsáhlá struktura, nemyslíte?
Syntax node
Nody jsou v obrázku označeny modře. Reprezentují deklarace, výrazy a další druhy top-level konstruktů (napadá Vás lepší překlad pro “declarations, statements, clauses, and expressions”?). V systému je každý druh syntax node reprezentován třídou dědící z bázové třídy SyntaxNode a množina těchto tříd není rozšiřitelná. Všechny druhy syntax node jsou neterminální symboly, což v teorii gramatik označuje uzly, které musí mít ještě další uzly pod sebou. Každý node má odkaz Parent na svého rodiče (null v případě kořene) a kolekci potomků. Taktéž má definované metody pro získání svých potomků podle jejich druhu (DescendantNodes, DescendantTokens, …).
Syntax token
Tokeny jsou v obrázku označeny zeleně. Reprezentují klíčová slova (int), identifikátory (a), literály (1 a 2) a interpunkci (; a + a =). Je trochu zavádějící napsat, že matematické operátory spadají do kategorie interpunkce, nicméně i podle jmen, které je reprezentují (o tom v sekci Kinds) tomu tak nasvědčuje. Tokeny jsou terminály gramatiky jazyka – nikdy nejsou rodičem jiného nodu nebo tokenu, ale jak lze na obrázku vidět, váže se na ně trivia. Zajímavostí tokenů je, že interně jsou všechny druhy reprezentovány jediným typem, SyntaxToken, což je z důvodu efektivnosti struct. Mezi tokeny se pak rozlišuje na základě proměnlivých vlastností.
Syntax trivia
Trivia je v obrázku označena bíle a šedivě. Reprezentuje bílé znaky (které nemusí být nutně obarvené bíle), komentáře a tzv. “preprocesor directives” (např. #PRAGMA). Tyto symboly význam kódu nemění, ale jak už jsem zmínil v první vlastnosti AST, je důležité si o nich udržovat přehled. Interně se trivia neuvažuje jako potomek tokenu (to by pak nebyl terminál), ale spíše jako jeho součást. Tedy každý token má kolekce LeadingTrivia a TrailingTrivia, což jsou předcházející symboly (bílé) a následující (šedivé). Systém je takový, že první token v dokumentu má přiřazené veškeré předcházející znaky a každý token má přiřazené následující znaky nacházející se na stejném řádku. Stejně jako v případě tokenů, veškerá trivia je reprezentována jediným hodnotovým typem SyntaxTrivia.
Kinds
Kind, neboli druh je z pohledu orientace asi nejvýznamnější vlastnost každého typu elementu stromu. Nabývá např. hodnoty VariableDeclaration pro node reprezentující deklaraci proměnné (v obrázku levý potomek kořene), nebo IntKeyword pro token reprezentující klíčové slovo zastupující typ Int32 a nakonec WhitespaceTrivia pro trivii zastupující mezeru. V kódu se s touto vlastností setkáte pod názvem RawKind, což je číslo, které je převoditelné do výčtového typu SyntaxKind, který je pro každý jazyk jiný.
Chyby
V první vlastnosti AST jsem zmínil, že strom počítá i s reprezentací chyb. Tedy lexikografických chyb, například chybějící symboly. Pokud nic nechybí, jen je to sémanticky špatně, pak se strom normálně postaví a až sémantická analýzy odhalí nedostatky. Pro ukázku schválně odmažu kousek kódu:
int a = 1 +
a z toho vznikne následující strom.
Můžete si všimnout, že kompilátor zdárně doplnil chybějící středník na konci řádku a také ví, že nám chybí node pro literál obsahující nějaký token, ať už literál nebo identifikátor. Chyby jsou označeny černě.
V tomto článku jsme se podrobně věnovali syntaktickým stromům z pohledu Roslynu. Rozdělili jsme stromové elementy na tři druhy, řekli si k nim příklady a informace, které nám poslouží v dalších dílech série, které budou praktické. Nejdříve si ukážeme, jak Roslyn nainstalovat a zprovoznit ve Visual Studiu 2013 (pro starší verze není dostupný), jak vykreslovat syntaktické stromy pro libovolný zdrojový kód a pak už nám nic nebude bránit k tomu, abychom naprogramovali nějaké diagnostiky, opravy kódu, atd.
Zdroj: http://roslyn.codeplex.com/wikipage?title=Overview&referringTitle=Home