Un'enumerazione è il modo classico / C-ish per definire i token, ma ciò non lo rende straordinariamente buono, proprio perché è difficile tenere traccia dei valori associati. Un token potrebbe contenere il valore di un valore letterale (ad esempio un numero o una stringa) e potrebbe anche contenere metadati quali offset del flusso di caratteri, numero di riga e colonna, ....
Un tipo di somma come nei linguaggi ML è decisamente preferibile: compatto come un campo enumerazione + dati, ma con una sicurezza del tipo appropriata. Sfortunatamente C #, C ++ e Java non supportano i tipi di somma corretti, quindi dobbiamo ricorrere a una gerarchia di classi. Questo non è l'ideale (sovraccarico di memoria più elevato, la gerarchia di classi non può essere sigillata), ma di solito è meglio della rotta di digitazione dinamica in cui il token ha un campo per un oggetto nullable.
Si noti che nel contesto di una gerarchia di token di un parser, va bene il downcast a tipi di token specifici. In alternativa, puoi utilizzare il pattern Visitor per abbinare specifici tipi di token in un modo sicuro per i tipi.
Ciononostante, generalmente sconsiglio una gerarchia di classi di token profondi. Invece di introdurre una classe Break
astratta, potresti invece aggiungere metodi / proprietà al tipo Token
di base come IsBreak => false
(e sovrascrivi nelle sottoclassi). Potrei divergere da ciò in due casi:
- per i token delle parole chiave, poiché in questo design ogni parola chiave sarà la sua classe. È molto più comodo se queste classi di parole chiave possono avere corpi quasi vuoti e ereditare qualsiasi valore di default dalla loro base.
- per gli operatori binari / unari, dal momento che un parser è spesso interessato solo se un token è un operatore binario piuttosto che decidere quale operatore binario è, ad es. quando si esegue l'analisi della precedenza dell'operatore.
Un'alternativa che voglio menzionare per completezza è l'analisi senza scanner in cui non si dispone di token espliciti. Invece, lexing / tokenizzazione sarebbero direttamente integrati nel parser. Mentre molti generatori di parser richiedono che i token siano analizzati in anticipo, ad es. PEG, Marpa o tecniche ad-hoc come la discesa ricorsiva no.