смотри, все просто
чтобы как-то работать с исходным кодом (транслировать, анализировать, оптимизировать), нужно какое-то удобное представление ее в памяти, отличное от текстового. Самый простой способ - дерево, абстраткное, чтобы всякий мусор, который важен при парсинге, забыть.
Парсер описывает конкретное синтаксические дерево, которое просто показывает, как исходный код был распаршен. Там удобное представление, чтобы получить его из парсера, но не самое удобное, чтобы с ним работать.
В простых случаях cst (конкретное) и ast (абстраткное) можно не разделять, если парсер достаточно гибкий, чтобы сразу строить ast. На каких-нибудь генераторах имхо это делать сложно, на комбинаторах просто
Значит парсер строит конкретное дерево, которое потом конвертируется а абстратное дерево. Потом мы с ним работаем, например строим из него абстрактное дерево другого языка (при трансляции), или промежуточное, которое потом в таргетное дерево.
Дерево вот как раз легко описывается в фп языках (но не обязательно только в фп) через adt, как вот на скрине выше. Это не грамматика, это определение типов.
type expr =
| val of int
| plus of expr * expr
1 + (2 + 3)
выглядит как
let x : expr = plus (val 1) (plus (val 2) (val 3))
в аст (и мб с отдельным конструктором скобок в cst)