
Che cos'è un analizzatore (parser)?
- VersionDude
- Parsing
- 5 min di lettura
Un analizzatore trasforma un flusso di caratteri in dati strutturati utilizzabili da un programma - il passo tra il testo grezzo e il significato.
Un analizzatore (parser) è il componente che legge un testo in ingresso e ne costruisce una rappresentazione strutturata con cui un programma può realmente lavorare. Il testo grezzo è solo una sequenza di caratteri senza significato intrinseco per il software; l'analizzatore è il passo che trasforma quel flusso piatto in qualcosa dotato di forma e gerarchia. Si colloca al confine tra il testo scritto da un umano o generato da una macchina e i dati strutturati di cui il resto di un programma ha bisogno.
Due fasi: tokenizzare, poi costruire un albero

La maggior parte degli analizzatori funziona in due passi distinti. Il primo passo, un tokenizzatore o lexer, raggruppa i caratteri in unità significative - tag, parole, numeri, operatori, simboli - così che l'ingresso non sia più caratteri individuali ma una sequenza di token. Il secondo passo, la costruzione dell'albero, dispone questi token in una gerarchia secondo una grammatica, producendo un albero strutturato. Separare questi due compiti mantiene ciascuno più semplice, ed è uno schema che rivedrai ancora e ancora.
La tokenizzazione da sola risolve molta ambiguità. Considera come i caratteri che formano un tag di apertura, un nome di attributo e un valore tra virgolette debbano essere riconosciuti come cose distinte prima che qualsiasi struttura possa essere costruita; il tokenizzatore è ciò che stabilisce queste distinzioni. È solo una volta che l'ingresso è un flusso pulito di token che il passo di costruzione dell'albero può ragionare su come si annidano e si relazionano gli uni agli altri.
Gli analizzatori sono ovunque
Gli analizzatori sono veramente ovunque non appena si comincia a guardare. I compilatori analizzano il codice sorgente in un albero sintattico astratto prima di generare istruzioni macchina; i browser analizzano l'HTML e il CSS per costruire le strutture che renderizzano; e le applicazioni ordinarie analizzano JSON, XML, YAML e file di configurazione a ogni avvio. Quasi ogni programma che accetta del testo in ingresso ha un analizzatore da qualche parte al suo interno, anche se l'autore non lo pensa mai con questo nome.
Perché l'uscita è un albero
L'uscita di un analizzatore è generalmente un albero, e il tipo di albero dipende dal dominio. Per un linguaggio di programmazione, è un albero sintattico astratto che cattura la struttura delle espressioni e delle istruzioni; per una pagina web, è il DOM, l'albero di elementi e nodi di testo che il browser costruisce a partire dall'HTML. In entrambi i casi, è l'albero, e non il testo originale, su cui operano i passi successivi.
Il motivo per cui un albero è l'uscita naturale è che la maggior parte dei linguaggi e dei formati di documento è intrinsecamente annidata. Un'espressione contiene sotto-espressioni, un elemento contiene elementi figli, un blocco di configurazione contiene blocchi annidati. Un elenco piatto di token non può catturare questo annidamento, ma un albero lo esprime direttamente, il che spiega perché l'analisi è così spesso descritta come la trasformazione di un flusso unidimensionale in una struttura a più livelli.
Cosa rende insolita l'analisi dell'HTML
Ciò che rende l'analisi dell'HTML insolita tra tutti questi esempi è il suo recupero degli errori standardizzato. Il markup del mondo reale è pieno di errori - tag non chiusi, elementi mal collocati, attributi in posizioni strane - eppure le pagine devono comunque visualizzarsi. La specifica HTML risponde a questo definendo esattamente come un analizzatore debba trattare ogni tipo di ingresso malformato, anziché lasciare che ogni implementazione indovini.
La conseguenza di questa specifica precisa è un'interoperabilità notevole. Poiché le regole di recupero degli errori sono dettagliate nel menu, ogni browser moderno costruisce lo stesso albero DOM a partire dallo stesso ingresso rotto, anziché che ciascuno improvvisi diversamente. È per questo che una pagina malformata si visualizza in modo coerente tra browser, e ciò contrasta con formati più rigorosi come XML, che rifiutano semplicemente un ingresso non ben formato anziché tentare di ripararlo.
Tollerante contro rigoroso, per progettazione
Questo contrasto mette in luce un compromesso di progettazione deliberato. Un analizzatore indulgente come quello dell'HTML privilegia la resilienza e il fatto che l'utente veda sempre qualcosa, al prezzo di lasciar passare in silenzio gli errori. Un analizzatore rigoroso come quello dell'XML privilegia la correttezza e la prevedibilità, al prezzo di rifiutare in partenza un ingresso imperfetto. Nessuno è universalmente migliore; ciascuno si addice al compito per cui è stato concepito, e riconoscere quale filosofia segue un formato ti dice molto sul suo comportamento.
Quando il parser si ferma: gli script
L’analisi dell’HTML ha una seconda particolarità, e ogni sviluppatore web prima o poi ci incappa, di solito per caso: il parser può essere fermato dal documento stesso che sta leggendo. Quando raggiunge un normale elemento script, non prosegue. MDN lo dice senza giri di parole: gli script senza gli attributi async, defer o type="module", così come gli script inline senza l’attributo type="module", vengono scaricati ed eseguiti immediatamente prima che il browser continui ad analizzare la pagina. L’analisi si ferma, lo script viene eseguito, e solo allora riprende.
Non è una stranezza, discende da ciò che a uno script è consentito fare. Uno script in esecuzione può scrivere nel documento e modificare markup che il parser non ha ancora raggiunto, quindi il parser non può portarsi avanti senza rischi. Questo solo vincolo spiega perché uno script collocato in alto nel documento ritarda tutto ciò che sta sotto, e perché mettere gli script in fondo al body è stato per anni il consiglio standard.
I due attributi che cambiano questo comportamento meritano una lettura precisa, perché non sono intercambiabili. Con async, lo script viene scaricato in parallelo all’analisi e valutato non appena è disponibile, il che significa che può interrompere l’analisi in un momento imprevedibile e, nelle parole di MDN, non garantisce alcun ordine di esecuzione specifico. Con defer, lo script viene eseguito dopo che il documento è stato analizzato, ma prima che venga emesso l’evento DOMContentLoaded, e gli script differiti vengono eseguiti nell’ordine in cui compaiono nel documento. async scambia l’ordine con la velocità; defer mantiene l’ordine e aspetta.
È una correzione utile al modello mentale. Un parser non è solo un traduttore da testo ad albero, è anche uno schedulatore: quando lavora, che cosa lascia accadere per strada e su che cosa si blocca determinano quanto in fretta una pagina diventa utilizzabile.
Un modello mentale per tutto lo stack
Non appena si comincia a vedere il software come una serie di analizzatori che trasformano il testo in struttura, gran parte dello stack web diventa più facile da ragionare. Il browser che analizza l'HTML in DOM, il motore che analizza il JavaScript in un albero sintattico, il server che analizza un corpo di richiesta JSON - sono tutte la stessa idea applicata in luoghi diversi. Questo unico prisma, del testo che diventa dati strutturati tramite un analizzatore, è uno dei modelli mentali più utili di tutta la programmazione.



È una correzione utile al modello mentale. Un parser non è solo un traduttore da testo ad albero, è anche uno schedulatore: quando lavora, che cosa lascia accadere per strada e su che cosa si blocca determinano quanto in fretta una pagina diventa utilizzabile.