La prima cosa che devi capire è che nessuno ti obbliga a scrivere un parser o un compilatore in un certo modo. In particolare, non è necessariamente il caso che il risultato di un parser debba essere un albero. Può essere qualsiasi struttura di dati adatta a rappresentare l'input.
Ad esempio, la seguente lingua:
prog:
definition
| definition ';' prog
;
definition: .....
può essere rappresentato come un elenco di definizioni. (Nitpickers indicherà che un elenco è un albero degenerato, ma comunque.)
In secondo luogo, non è necessario conservare l'albero di analisi (o qualsiasi struttura di dati restituita dal parser). Al contrario, i compilatori sono generalmente 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 si sente parlare di alberi di analisi , alberi Syntac astratti , alberi di sintassi in calcestruzzo , ecc, sempre sostituire da struttura di dati idonei allo scopo determinato , e che stai bene.