Они обычно используются в парсерах LL (сверху вниз), чтобы проверить, не возникнет ли работающий парсер в любой ситуации, когда существует более одного способа продолжить синтаксический анализ.
Если у вас есть альтернатива A | B
, а также FIRST(A) = {"a"}
и FIRST(B) = {"b", "a"}
, тогда у вас будет конфликт ПЕРВЫЙ / ПЕРВЫЙ, потому что, когда на входе появляется «а», вы не будете знать, расширять ли А или Б. (Предполагается, что прогноз - 1).
С другой стороны, если у вас нетерминал, который можно обнулять, например, AOpt: ("a")?
, тогда вы должны убедиться, что FOLLOW(AOpt)
не содержит «a», потому что иначе вы бы не знали, расширять AOpt или нет. здесь: S: AOpt "a"
Либо S, либо AOpt может потреблять «a», что дает нам конфликт FIRST / FOLLOW.
FIRST-наборы также могут использоваться во время анализа для повышения производительности. Если у вас есть Nullable нетерминальный NullableNt, вы можете расширить его, чтобы увидеть, может ли он что-либо потреблять, или может быть быстрее проверить, содержит ли FIRST(NullableNt)
следующий токен, а если нет, то просто проигнорировать его (обратный просмотр против прогнозирующего анализа). Другим улучшением производительности может быть дополнительное предоставление лексическому сканеру текущего набора FIRST, чтобы сканер не пробовал все возможные терминалы, а только те, которые в настоящее время разрешены контекстом. Это конфликтует с зарезервированными терминалами, но они не всегда нужны.
В парсерах снизу вверх возникают конфликты разного типа, а именно: уменьшение / уменьшение и смещение / уменьшение. Они также используют наборы предметов для обнаружения конфликтов, а не ПЕРВЫЙ, СЛЕДУЮЩИЙ.
Ваша грамматика не будет работать с LL-парсерами, потому что она содержит левую рекурсию. Но ПЕРВЫМИ наборами для E, T и V будут {id} (при условии, что ваш T := T*V | T
означает T := T*V | V
).