La première chose que vous devez comprendre est que personne ne vous oblige à écrire un analyseur ou un compilateur d'une certaine manière. Plus précisément, il n'est pas nécessairement vrai que le résultat d'un analyseur doit être un arbre. Il peut s'agir de n'importe quelle structure de données appropriée pour représenter l'entrée.
Par exemple, la langue suivante:
prog:
definition
| definition ';' prog
;
definition: .....
peut être représenté comme une liste de définitions. (Nitpickers indiquera qu'une liste est un arbre dégénéré, mais de toute façon.)
Deuxièmement, il n'est pas nécessaire de conserver l'arbre d'analyse (ou la structure de données retournée par l'analyseur). Au contraire, les compilateurs sont généralement construits comme une séquence de passes, qui transforment les résultats de la passe précédente. Par conséquent, la présentation globale d'un compilateur pourrait être la suivante:
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: Si vous entendez les gens parler des arbres parse , arbres abstraits Syntac , arbres syntaxiques en béton , etc., toujours remplacer par la structure de données adaptées à l'objectif donné , et vous êtes bien.