Перше, що вам потрібно зрозуміти, це те, що ніхто не змушує вас певним чином написати парсер чи компілятор. Зокрема, необов’язково буває, що результатом аналізатора має бути дерево. Це може бути будь-яка структура даних, яка підходить для представлення вхідних даних.
Наприклад, наступна мова:
prog:
definition
| definition ';' prog
;
definition: .....
може бути представлений у вигляді списку визначень. (Nitpickers вкаже, що список - це вироджене дерево, але все одно.)
По-друге, не потрібно триматися на дереві розбору (або будь-якої структури даних, яку повернув аналізатор). Навпаки, компілятори зазвичай будуються як послідовність проходів, які перетворюють результати попереднього проходу. Отже, загальний макет компілятора може бути таким:
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
Підсумок: Якщо ви чуєте, як люди говорять про дерева розбору , абстракціоновані синтаксичні дерева , конкретні синтаксичні дерева тощо, завжди підміняйте структуру даних, що підходить для даної мети , і ви все добре.