La prima cosa che devi capire è che nessuno ti obbliga a scrivere un parser o un compilatore in un certo modo. Nello specifico, non è necessariamente il caso che il risultato di un parser debba essere un albero. Può essere una qualsiasi struttura dati adatta a rappresentare l'input.
Ad esempio, la seguente lingua:
prog:
definition
| definition ';' prog
;
definition: .....
può essere rappresentato come una lista di definizioni. (Nitpickers farà notare che una lista è un albero degenerato, ma comunque.)
In secondo luogo, non è necessario mantenere l'albero di analisi (o qualsiasi altra struttura dati restituita dal parser). Al contrario, i compilatori di solito sono costruiti come una sequenza di passaggi, che trasformano i risultati del passaggio precedente. Quindi il layout generale di un compilatore potrebbe essere così:
parser :: String -> Maybe [Definitions] -- parser
pass1 :: [Definitions] -> Maybe DesugaredProg -- desugarer
pass2 :: DesugaredProg -> Maybe TypedProg -- type checker
pass3 :: TypedProg -> Maybe AbstractTargetLang -- code generation
pass4 :: AbstractTargetLang -> Maybe String -- pretty printer
compiler :: String -> Maybe String -- transform source code to target code
compiler source = do
defs <- parser source
desug <- pass1 defs
typed <- pass2 desug
targt <- pass3 typed
pass4 targt
Bottom Line: se senti gente parlare di parse trees , alberi syntac astratti , alberi di sintassi concreti ecc., sostituisci sempre < em> struttura dati adatta allo scopo , e stai bene.