Il presente articolo è stato tradotto automaticamente.

Il programmatore al lavoro

La nascita di Roslyn

Joe Hummel
Ted Neward

Ted NewardPer alcuni anni, vari professionisti informatici, leader di pensiero e sapientoni hanno sostenuto l'idea di linguaggi specifici di dominio (DSL) come un modo di avvicinarsi soluzioni a problemi software. Questo sembra particolarmente appropriato se la sintassi DSL è qualcosa di "utenti occasionali" consentono di adattare e modificare le regole di business in un sistema. Questo è il Santo Graal di software per molti sviluppatori, costruendo un persone di sistema può mantenere in proprio quando il business ha bisogno di cambiare.

Una delle principali critiche di DSL, tuttavia, è il fatto che scrivere un compilatore è un "arte perduta". Non è raro per i programmatori da tutti i ceti sociali a considerare la creazione di un compilatore o un interprete come una sorta di arte oscura.

Alla conferenza Build 2014 quest'anno, Microsoft formalmente annunciato uno dei segreti mantenuti peggio nell'ecosistema di sviluppo Microsoft .NET Framework — il sourcing aperto di Roslyn. Questo è il sistema rinnovato/ricostruito compilatore che sottende i linguaggi c# e Visual Basic . Per alcuni, questa è un'occasione per Microsoft per mettere le sue lingue nella comunità open source e raccogliere i frutti — correzioni di bug, miglioramenti, revisione pubblica delle nuove funzionalità del linguaggio e così via. Per gli sviluppatori, è un'opportunità per prendere uno sguardo più approfondito come compilatori (e interpreti — sebbene Roslyn è focalizzato sulla compilation dato le lingue in questione) lavoro sotto il cofano.

Per ulteriori sfondo (e consigli di installazione) controlla la pagina di Roslyn CodePlex a roslyn.codeplex.com. Come sempre con bit non ancora rilasciato, esso è altamente raccomandato che è farlo su una macchina virtuale o una macchina non si preoccupano troppo.

Fondamenti di Roslyn

Ad alto livello, l'obiettivo di un compilatore è tradurre ingresso programmatore (codice sorgente) in output eseguibile, ad esempio un assembly .NET o .exe nativo. Mentre i nomi esatti per moduli all'interno di un compilatore variano, in genere pensiamo a un compilatore come spezzato in due parti fondamentali: un front-end e back-end (Vedi Figura 1).

Design di alto livello compilatore
Design di alto livello compilatore figura 1

Una delle principali responsabilità del front-end è quello di verificare l'esattezza di formattazione del codice sorgente in ingresso. Come con tutti i linguaggi di programmazione, non esiste un formato specifico, i programmatori devono seguire per mantenere le cose chiare ed inequivocabili per la macchina. Si consideri, ad esempio, la seguente istruzione c#:

if x < 0   <-- syntax error!
  x = 0;

Questo non è sintatticamente corretto, perché se condizioni devono essere circondati da (), in questo modo:

if (x < 0)
  x = 0;

Una volta che il codice viene analizzato, il back-end è responsabile di convalida più profonda della sorgente, ad esempio violazioni di sicurezza del tipo:

string x = "123";
if (x < 0)                   <-- semantic error!
  x = 0;                     <-- semantic error!

Per inciso, questi esempi sono le decisioni di progettazione intenzionale dell'implementatore di lingua. Sono soggetti a lunghi dibattiti su quali sono "meglio" di altri. Per sentire di più, visitare qualsiasi forum online di programmazione e digitare in, "D00d vostra sux lingua." Presto troverete invischiati in una sessione "educativa" che sarà certamente uno da ricordare.

Supponendo che non siano presenti errori sintattici o semantici, compilazione continua e back-end traduce l'input in un programma equivalente nella lingua di destinazione desiderata.

Più in profondità nelle profondità

Anche se si potrebbe prendere l'approccio di due parti con i linguaggi più semplici, più spesso un compilatore/interprete del linguaggio è suddiviso in molto di più. Al successivo livello di complessità, la maggior parte dei compilatori si organizzano per agire in sei fasi principali, due nella parte anteriore e quattro nel back-end (Vedi Figura 2).

le principali fasi di un compilatore
Figura 2 le principali fasi di un compilatore

Il front-end esegue le prime due fasi: analisi lessicale e l'analisi. L'obiettivo dell'analisi lessicale è per leggere il programma di input e output i token — le parole chiave, punteggiatura, identificatori e così via. La posizione di ogni token è anche mantenuta, quindi il formato del programma non è perduto. Si supponga che il seguente frammento di programma comincia all'inizio del file di origine:

// Comment
if (score>100)
  grade = "A++";

L'output di analisi lessicale sarebbe questa sequenza di token:

IfKeyword                       @ Span=[12..14)
OpenParenToken              @ Span=[15..16)
IdentifierToken                  @ Span=[16..21), Value=score
GreaterThanToken             @ Span=[21..22)
NumericLiteralToken           @ Span=[22..25), Value=100
CloseParenToken              @ Span=[25..26)
IdentifierToken                  @ Span=[30..35), Value=grade
EqualsToken                    @ Span=[36..37)
StringLiteralToken             @ Span=[38..43), Value=A++
SemicolonToken               @ Span=[43..44)

Ogni token trasporta informazioni aggiuntive, come ad esempio la posizione di inizio e fine (Span) come misurato dall'inizio del file di origine. Avviso IfKeyword inizia alla posizione 12. Ciò è dovuto il commento che si estende su [0..10) e il fine-linea personaggi che arco [10..12). Mentre tecnicamente non gettoni, uscita dall'analizzatore lessicale in genere include informazioni su uno spazio vuoto, compreso i commenti. Nel compilatore .NET, whitespace è convogliato come curiosità di sintassi.

La seconda fase del compilatore è l'analisi. Il parser funziona mano nella mano con l'analizzatore lessicale per effettuare l'analisi di sintassi. Il parser esegue la maggior parte del lavoro, richiesta di token dall'analizzatore lessicale come controlla il programma ingresso contro le varie regole grammaticali della lingua di origine. Ad esempio, tutti i programmatori c# conoscono la sintassi di un se istruzione:

if  (  condition  )  then-part  [ else-part ]

Il [...] simboleggia la parte else è opzionale. Il parser applica questa regola corrispondente token e applicando regole aggiuntive per gli elementi sintattici più complessi come condizione e poi parte:

void if( )
{
  match(IfKeyword);
  match(OpenParenToken);
  condition();
  match(CloseParenToken);
  then_part();
  if (lookahead(ElseKeyword))
  else_part();
}

La funzione match(T) chiama l'analizzatore lessicale per ottenere il prossimo token e controlla per vedere se questo token corrisponde a T. Compilazione continua normalmente se corrisponde. In caso contrario, segnala un errore di sintassi. Il più semplice degli analizzatori di utilizzare una funzione di corrispondenza per generare un'eccezione su un errore di sintassi. Ciò arresta efficacemente compilazione. Qui è un tale implementazione:

void match(SyntaxToken T)
{
  var next = lexer.NextToken();
  if (next == T)
  ;  // Keep going, all is well:
  else
  throw new SyntaxError(...);
}

Fortunatamente per noi, il compilatore .NET contiene un parser molto più sofisticato. È in grado di continuare a fronte di errori di sintassi lordo.

Supponendo che non vi erano senza errori di sintassi, il front-end è fatto essenzialmente. Esso ha, ma un'unica attività rimanente — per convogliare gli sforzi per il back-end. La forma in cui questo viene memorizzato internamente è conosciuta come sua rappresentazione intermedia — o IR. (Nonostante la somiglianza nella terminologia, un IR ha nulla a che fare con il .NET Common Intermediate Language). Il parser nel compilatore .NET costruisce un albero di sintassi astratta (AST) come l'IR e passa questo albero al back-end.

Gli alberi sono una naturale IR, data la natura gerarchica dei programmi c# e Visual Basic . Un programma conterrà una o più classi. Una classe contiene proprietà e metodi, proprietà e metodi contengono affermazioni, dichiarazioni contengono spesso blocchi e blocchi contengono istruzioni aggiuntive. L'obiettivo di un AST è quello di rappresentare il programma basato sulla sua struttura sintattica. il "astratto" in AST denota l'assenza di zucchero sintattico come; e ().  Ad esempio, si consideri la seguente sequenza di c# dichiarazioni (assumere che queste compilare senza errori):

sum = 0;
foreach (var x in A)   // A is an array:
  sum += x;
avg = sum / A.Length;

Ad alto livello, l'AST per questo frammento di codice sarebbe simile Figura 3.

albero di sintassi astratta ad alto livello per c# codice frammento (dettagli mancanti per semplicità)
Figura 3 albero di sintassi astratta ad alto livello per c# codice frammento (dettagli mancanti per semplicità)

L'AST acquisisce le informazioni necessarie sul programma: le affermazioni, l'ordine delle dichiarazioni, i pezzi di ogni istruzione e così via. La sintassi inutile viene scartata, come tutti i punti e virgola. La caratteristica fondamentale di capire circa l'AST in Figura 3 è che si coglie la struttura sintattica del programma.

In altre parole, è come il programma è scritto, non come esso exe­cutes. Si consideri l'istruzione foreach, che esegue un ciclo zero o più volte come esso scorre un insieme. L'AST acquisisce i componenti dell'istruzione foreach — la variabile del ciclo, la raccolta e il corpo. Che cosa l'AST non trasmettere è che foreach possono ripetere più e più volte. Infatti, se si guarda l'albero, non c'è nessuna freccia nell'albero per significare come foreach esegue. L'unico modo per sapere è conoscendo la parola chiave foreach = = ciclo.

ASTs sono un IR perfettamente buono, con uno dei principale vantaggi: Sono facili da costruire e capire. Lo svantaggio è che le analisi più sofisticate, come quelle utilizzate nel back-end del compilatore, sono più difficili da eseguire su un AST. Per questo motivo, i compilatori spesso mantengono IRs multiple, tra cui un'alternativa comune all'AST. Questa alternativa è il grafico di flusso di controllo (CFG), che rappresenta un programma basato sul suo flusso di controllo: loop, istruzioni if-then-else, eccezioni e così via. (Parleremo di questo più nella colonna successiva).

Il modo migliore per imparare come la AST è utilizzato nel compilatore .NET è tramite il visualizzatore di sintassi Roslyn. Questo è installato come parte di Roslyn SDK. Una volta installato, aprire qualsiasi programma c# o Visual Basic in Visual Studio 2013, posizionare il cursore nella riga fonte di interesse e aprire il visualizzatore. Vedrai il menu Visualizza, altre finestre e Roslyn sintassi Visualizer (vedere Figura 4).

il visualizzatore Roslyn sintassi in Visual Studio 2013
Figura 4 il visualizzatore Roslyn sintassi in Visual Studio 2013

Come un esempio concreto, si consideri l'if istruzione abbiamo analizzati in precedenza:

 

// Comment
  if (score>100)
    grade = "A++";

Figura 5 illustrato il frammento corrispondente AST costruito dal compilatore .NET.

albero di sintassi astratta costruito dal compilatore .NET per IfStatement
Figura 5 albero di sintassi astratta costruito dal compilatore .NET per IfStatement

Come con un sacco di cose, l'albero sembra schiacciante in un primo momento. Ricordate due cose, però. Uno, l'albero è semplicemente un'espansione delle precedenti dichiarazioni di origine, quindi è realmente abbastanza facile camminare attraverso l'albero e vedere come esso mappe torna alla sorgente originale. E due, l'AST è destinato al consumo di macchina, non per gli esseri umani. Generalmente l'unica volta che un umano sembra l'AST è un parser di debug. Tenete a mente, pure, che un corso più completo su lexing e l'analisi è ben oltre l'ambito dello spazio che abbiamo qui. Ci sono un sacco di risorse disponibili per coloro che vogliono immergersi più profondo in questo esercizio. L'obiettivo qui è un'introduzione delicata, non un'immersione profonda.

Conclusioni

Non abbiamo finito con Roslyn con uno sforzo d'immaginazione, quindi rimanete sintonizzati. E sei interessato a immersioni più profonde in Roslyn, possiamo suggerire l'installazione di Roslyn. Poi dare un'occhiata ad alcuni della documentazione, a partire con la pagina di Roslyn CodePlex.

Se si desidera immergersi più profondamente l'analisi e la lexing, ci sono numerosi libri disponibili. C'è il venerabile "libro di drago", noto anche come "compilatori: Principi, tecniche & Strumenti"(Addison Wesley, 2006). Se siete interessati a un più.NET-centriche avvicinarsi, considerare "compilazione per il .NET Common Language Runtime (CLR)" di John Gough (Prentice Hall, 2001), o di Ronald Mak "scrittura di compilatori e Interpeters: Un approccio di ingegneria del Software"(Wiley, 2009). Codificazione felice!


Joe Hummelè un professore associato di ricerca presso l'Università dell'Illinois, Chicago, un creatore di contenuti per Pluralsight.com, un Visual C++ MVP e consulente privato.  Ha conseguito un pH.d. presso la UC Irvine nel campo del calcolo ad alte prestazioni ed è interessata in tutte le cose in parallele. Egli risiede a Chicago, e quando egli non è vela può essere raggiunto a joe@joehummel.net.

Ted Neward è CTO di iTrellis, una società di consulenza servizi. Ha scritto più di 100 articoli e autore di una dozzina di libri, tra cui "Professional F # 2.0" (Wrox, 2010). Egli è un MVP F # e parla a conferenze in tutto il mondo. Egli consulta e mentors regolarmente — contattarlo al ted@tedneward.com o ted@itrellis.com se siete interessati.

Grazie al seguente Microsoft esperto tecnico per la revisione di questo articolo: Dustin Campbell