Ключом к созданию чего-то чисто функционального является то, что все состояния должны быть переданы как параметры функции и возвращаемые значения. В вашем случае состояние является входным потоком; то есть список, который еще не был проанализирован.
Один из способов сделать это состоит в том, чтобы каждая функция синтаксического анализа возвращала логическое значение, указывающее, увидела ли она то, что ожидалось, а также возвращала непарсированный остаток списка.
Это означает, что вы будете использовать функции разбора такого типа:
aplus : string list -> bool * string list
bplus : string list -> bool * string list
Если вы просто ищете a, а затем b, вы можете написать его следующим образом:
let parse strings =
let (good, rest) = aplus strings in
if not good then false
else let (good, rest) = bplus rest in
good && rest = []
Это довольно громоздкий код. Это может быть аккуратнее, если у вас есть функция, которая работает как «&&», за исключением того, что она передает непарсированный список следующей функции. Такие вещи часто возникают, когда вы начинаете писать чисто функциональный код.
Было бы много других способов структурировать синтаксический анализ, некоторые, вероятно, намного лучше, чем описанные выше. Но ключ (IMHO) в том, что ваши функции синтаксического анализа должны возвращать остаток входного потока, а также указание успеха / неудачи.
(Обратите внимание, что здесь пока нет рекурсии, но это произойдет, когда вы напишите отдельные функции синтаксического анализа.)