Можно упростить, отделив приложение от других выражений:
EXPR -> l{v} APPL "abstraction"
-> (APPL) "brackets"
-> {v} "variable"
APPL -> EXPR + "application"
Единственным отличием вашего подхода является то, что приложение представляется в виде списка выражений, поскольку abcd
может быть неявно прочитано как (((ab)c)d)
, так что вы можете хранить его как abcd
при разборе.
На основе этой грамматики может быть создан простой анализатор рекурсивного спуска с одним символом предвидения:
EXPR: 'l' // read character, then APPL, return as abstraction
'(' // read APPL, read ')', return as-is
any // read character, return as variable
eof // fail
APPL: ')' // unread character, return as application
any // read EXPR, append to list, loop
eof // return as application
Корневым символом является APPL, конечно. В качестве шага после анализа вы можете превратить ваш список APPL = EXPR в дерево приложений. Рекурсивный спуск настолько прост, что вы можете легко превратиться в императивное решение с явным стеком, если хотите.