Lo primero que debes entender es que nadie te obliga a escribir un analizador o compilador de cierta manera. Específicamente, no es necesariamente el caso que el resultado de un analizador debe ser un árbol. Puede ser cualquier estructura de datos que sea adecuada para representar la entrada.
Por ejemplo, el siguiente idioma:
prog:
definition
| definition ';' prog
;
definition: .....
se puede representar como una lista de definiciones. (Nitpickers señalará que una lista es un árbol degenerado, pero de todos modos).
En segundo lugar, no es necesario retener el árbol de análisis (o cualquier estructura de datos que haya devuelto el analizador). Por el contrario, los compiladores generalmente se construyen como una secuencia de pasadas, que transforman los resultados de la pasada anterior. Por lo tanto, el diseño general de un compilador podría ser así:
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
Conclusión: Si se oye hablar de árboles de análisis sintáctico , árboles abstractos Syntac , los árboles de sintaxis concretas etc., siempre sustituyen por estructura de datos adecuada para el propósito dado , y ya está bien.