A primeira coisa que você precisa entender é que ninguém o obriga a escrever um analisador ou compilador de uma certa maneira. Especificamente, não é necessariamente o caso em que o resultado de um analisador deve ser uma árvore. Pode ser qualquer estrutura de dados adequada para representar a entrada.
Por exemplo, o seguinte idioma:
prog:
definition
| definition ';' prog
;
definition: .....
pode ser representado como uma lista de definições. (Nitpickers apontam que uma lista é uma árvore degenerada, mas de qualquer maneira.)
Segundo, não há necessidade de manter a árvore de análise (ou qualquer estrutura de dados que o analisador retornou). Pelo contrário, os compiladores geralmente são construídos como uma sequência de passes, que transformam os resultados do passe anterior. Portanto, o layout geral de um compilador pode ser assim:
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 você ouvir as pessoas falam sobre árvores de análise , árvores Syntac abstratas , árvores de sintaxe concretas etc., sempre substituir pela estrutura de dados adequado para o propósito determinado , e você está bem.