← Torna ai Quaderni
Quaderno storico 10 min di lettura

Espressioni regolari: guida base

La guida del 2006 alle espressioni regolari, con un capitolo nuovo: una sola regex scritta male può fermare un server.

Autore: Vincenzo Lombino
#ComputerScience#Regex#Performance

1. Le basi pratiche: validare un form web

Le espressioni regolari (regex) sono uno strumento essenziale per chiunque sviluppi software: permettono di descrivere stringhe testuali che presentano al loro interno una regolarità formale, compattando decine di controlli procedurali in un'unica espressione.

Nella guida pratica «Espressioni Regolari (Guida Base)», il punto di partenza era un problema comune a ogni applicazione web: validare i campi compilati in un form utente:

  • Nome: formato da una sola parola, contenente solo lettere dell'alfabeto (incluse lettere accentate). Nel nostro esempio non è obbligatorio: ^[a-zA-Zàòèéùì]*$
  • Cognome: formato da una o più parole, può contenere lettere, spazi e apici. Campo obbligatorio: ^[a-zA-Zàòèéùì' ]+$
  • Email: stringa alfanumerica con punti e trattini, seguita da chiocciola @, dominio e TLD da 2 a 4 lettere: ^[a-zA-Z0-9_.]+@[a-zA-Z0-9-]+\.[a-zA-Z]{2,4}$
  • Numero di telefono: prefisso e numero separati da trattino: ^[0-9]+-[0-9]+$

2. La cassetta degli attrezzi: classi, range, quantificatori e ancore

La composizione di queste regole si fonda su pochi operatori fondamentali:

  • Le classi [...] e l'operatore range -: le parentesi quadre individuano una singola occorrenza di uno dei caratteri inclusi. L'operatore - definisce un intervallo ordinato: [a-z] per le minuscole, [0-9] per le cifre, o classi combinate come [a-fA-F0-9] per identificare numeri esadecimali.
  • I quantificatori di ripetizione: l'asterisco * individua zero o più ripetizioni consecutive (ammettendo l'insieme vuoto per campi opzionali); il più + richiede una o più ripetizioni (obbligatorietà); le graffe {min,max} vincolano il numero esatto o l'intervallo di ripetizioni, ad esempio [0-9]{3,4}-[0-9]{7} per prefisso e utenza.
  • L'escape \: anteposto a un metacarattere (come il punto \., il trattino \- o la barra \/), ne annulla la funzione speciale e lo tratta come carattere letterale.
  • Le ancore ^ e $: l'ancora ^ impone la corrispondenza dall'inizio della riga e $ al termine della stessa. Senza le ancore, la regex [0-9]+ risulterebbe vera anche su stringhe come abc123xyz, vanificando la validazione formale.

3. La trappola dell’ingordigia: match greedy e classi negate

Gli operatori di ripetizione sono per impostazione predefinita avidi (greedy): cercano di catturare la sequenza più lunga possibile che soddisfa il pattern.

Se applichiamo l'espressione <.+> alla stringa HTML:

html
<HTML><HEAD><TITLE>ESPRESSIONI REGOLARI</TITLE></HEAD><BODY></BODY></HTML>

il motore non isolerà i singoli tag: catturerà tutto dal primo < fino all'ultimo > dell'intera riga, inglobando l'intero documento in un unico match indesiderato.

Per risolvere il problema esistono due approcci con prestazioni profondamente diverse:

  • Il quantificatore riluttante <.+?>: ordina al motore di fermarsi alla prima occorrenza di >, ma richiede continui controlli passo-passo.
  • La classe negata <[^<>]+>: definisce esplicitamente che tra i delimitatori può comparire qualsiasi carattere tranne < e >. L'automa avanza in modo lineare e deterministico senza alcuna ambiguità.

Allo stesso modo, per isolare frasi delimitate da un punto si scrive [^.]+, sfruttando la negazione della classe tramite il metacarattere ^ posizionato all'interno delle quadre.

Se dovete unicamente rimuovere gli spazi da una stringa o verificare un prefisso, non utilizzate una regex: le funzioni native di stringa (includes, indexOf, replaceAll) sono di un ordine di grandezza più veloci e azzerano i rischi di elaborazione.

4. Il collasso in produzione: ReDoS e catastrophic backtracking

La maggior parte dei linguaggi di programmazione moderni (PHP, JavaScript, Python) utilizza motori regex basati su automi a stati finiti non deterministici (NFA). Quando un'espressione contiene quantificatori annidati o alternanze sovrapposte — come ^(a+)+$ oppure (a|a+)+$ — il motore esplora ricorsivamente tutti i possibili raggruppamenti.

Su una stringa valida come aaaaa, il match è istantaneo. Ma davanti a un input che non soddisfa la condizione finale — come trenta lettere a seguite da un punto esclamativo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa! — l'automa non deterministico tenta ogni combinazione di suddivisione dei caratteri prima di dichiarare il fallimento.

Il numero di passi computazionali cresce esponenzialmente in O(2n). Con una stringa di appena 30-40 caratteri, i passi superano il miliardo: un singolo thread di processo rimane bloccato al 100% di CPU per minuti.

Questo fenomeno, noto come Regular Expression Denial of Service (ReDoS), non è un'ipotesi teorica: il 2 luglio 2019 una singola espressione regolare con backtracking catastrofico (.*.*=.*) distribuita nelle regole WAF di Cloudflare saturò le CPU di tutti i server edge a livello globale, provocando 27 minuti di disservizio planetario.

5. L’era dell’AI: decision model, estrazione dati e la matematica sotto il cofano

Fino a poco tempo fa, i modelli linguistici mostravano un limite diventato celebre: non erano in grado di contare in modo affidabile quante lettere «r» ci fossero nella parola strawberry. Il motivo era strutturale: le reti neurali non leggono singoli caratteri, ma operano su sequenze di token pre-elaborati. Per decenni gli sviluppatori hanno scritto migliaia di righe di regex complesse, fragili e piene di lookaround per estrarre dati da testi disordinati, documenti o formati eterogenei.

Con l'avvento dei moderni decision model e dei modelli di ragionamento, questo scenario è cambiato radicalmente. I modelli comprendono il contesto semantico, gestiscono refusi, estrapolano tabelle e possono riscrivere testi caotici con una flessibilità inarrivabile per qualsiasi pattern rigido. A prima vista, le espressioni regolari sembrerebbero uno strumento destinato all'obsolescenza.

L'ironia architetturale è che l'AI non ha eliminato le espressioni regolari: le usa sotto il cofano proprio per ottimizzare i processi più critici:

  • Tokenizzazione e pre-processing: gli algoritmi alla base dei modelli (come il BPE di tiktoken) spezzano il flusso di testo grezzo usando proprio espressioni regolari compilate ad altissima velocità prima che intervenga qualsiasi calcolo tensoriale.
  • Structured Outputs e decoding vincolato: quando chiediamo a un modello di generare un JSON conforme a uno schema rigido (tecniche come quelle di Outlines, vLLM o le API di Structured Outputs), i motori di inferenza compilano lo schema o la regex richiesta in un automa a stati finiti deterministico (DFA). Ad ogni passo di generazione autoregressiva, l'automa applica una maschera sui logit (logit masking), azzerando la probabilità di qualsiasi token che violi la grammatica formale. È la matematica degli automi e dei linguaggi regolari che obbliga un sistema probabilistico a essere matematicamente deterministico.

Qui emerge il punto fondamentale per chi fa architettura software: conoscere i principi dell'informatica permette di fare all'AI le domande giuste.

Un approccio ingenuo delega tutto al modello: invia 50 megabyte di log o dump testuali nel contesto chiedendo di riscrivere o estrarre i dati via prompt. Il risultato sono minuti di latenza, costi spropositati per bruciare token, saturazione del contesto e il rischio continuo di allucinazioni.

Chi conosce i linguaggi formali e la complessità computazionale fa una richiesta diversa: usa la capacità di ragionamento del modello su un piccolo campione per individuare la struttura del problema, facendosi generare una soluzione matematica e deterministica — un DFA lineare, uno script con metodi nativi di stringa o una regex priva di backtracking. Quel codice elabora poi milioni di record in memoria locale a costo zero, in tempo lineare e con comportamento prevedibile. L'AI diventa così un acceleratore per generare logica precisa, non una macchina probabilistica da mantenere perennemente in linea per fare semplici controlli di formato.