Salta ai contenuti

Estrazione del testo (PEG e regex)

Quando automatizzi il browser, incontri spesso dati “grezzi”: testo formattato in modo incoerente, con errori di battitura o con tipi di informazioni diversi mescolati in un’unica stringa.

JavaScript e Lua si affidano alle espressioni regolari (regex) per questo compito, mentre il motore Janet di Guida include un motore Parsing Expression Grammar (PEG) integrato. Le PEG sono una potente alternativa alle regex e rendono l’estrazione di testo complessa più facile da leggere, scrivere e mantenere.

Le regex sono eccellenti per pattern semplici, ma diventano rapidamente illeggibili, quasi un linguaggio a se stante molto poco propenso a futura manutenzione, quando si lavora con strutture complesse o annidate.

Immagina di estrarre prezzo e ID di un prodotto da una stringa non strutturata come "Articolo: SuperWidget (ID: 98765) - Solo $49.99 oggi!".

const text = "Articolo: SuperWidget (ID: 98765) - Solo $49.99 oggi!";
// La regex è densa e si affida a gruppi di cattura estratti per indice
const regex = /\\(ID:\\s*(\\d+)\\).*?\\$([0-9.]+)/;
const match = text.match(regex);
if (match) {
const id = match[1];
const price = match[2];
g.log(`ID trovato: ${id}, prezzo: ${price}`);
}

Funziona, ma la regex è fragile. Se il formato cambia anche di poco, la regex fallisce; modificarla richiede un notevole sforzo mentale.

Il modulo peg di Janet permette di costruire parser in modo composizionale. Invece di una singola stringa enorme e criptica, definisci una grammatica come dizionario di regole leggibili.

(def text "Articolo: SuperWidget (ID: 98765) - Solo $49.99 oggi!")
# Definisci una grammatica pulita e composizionale
(def product-grammar
(peg/compile
~{:id-rule (sequence "(ID: " (capture (some :d)) ")")
:price-rule (sequence "$" (capture (some (choice :d "."))))
# La regola principale scorre il testo cercando le nostre regole
:main (any (choice
:id-rule
:price-rule
1))}))
# Esegui la grammatica sul testo
(def results (peg/match product-grammar text))
(if results
(do
(def id (get results 0))
(def price (get results 1))
(g.log (string "ID trovato: " id ", prezzo: " price))))
  1. Leggibilità: le regole hanno un nome (:id-rule, :price-rule) e possono richiamarsi a vicenda. Non devi decifrare una stringa regex di 50 caratteri.
  2. Composizione: puoi costruire parser complessi combinando regole più piccole e testate.
  3. Assenza di ambiguità: a differenza delle regex, le PEG non fanno backtracking nello stesso modo imprevedibile. Corrispondono esattamente a ciò che definisci, rendendo le prestazioni prevedibili.
  4. Strutture dati: il modulo peg di Janet può costruire strutture dati complesse (array, dizionari) direttamente dalla fase di parsing, invece di restituire solo stringhe.
  • Usa JavaScript/Lua (regex) per validazioni semplici, per esempio “questa stringa sembra un indirizzo email?”.
  • Usa Janet (PEG) quando devi estrarre più elementi correlati da un blocco di testo non strutturato, oppure quando la formattazione del sito di destinazione è molto variabile.