Процедура Proc_A(str,k); Выбор str[k] из: a: Rule(3); вернуть k+1; b: Rule(4); вернуть Proc_A(str,k+1); c: Rule(5); вернуть Proc_C(str,k+1); иначе вернуть 0; |
Процедура Proc_B(str,k); Выбор str[k] из: a: Rule(7); вернуть Proc_B(str,k+1); b: Rule(6); вернуть k+1; c: Rule(8); вернуть Proc_C(str,k+1); иначе вернуть 0; |
Процедура Proc_C(str,k); Rule(9); i:=Proc_A(str,k); если i=0 вернуть 0; если str[i]≠’a’ вернуть 0; i:=Proc_B(str,i+1); если i=0 вернуть 0; если str[i]≠’b’ вернуть 0; вернуть i+1; |
Пусть цепочка a = ‘acbaabb’. Разбор начинается с вызова Proc_S(a,1). Дальнейшие вызовы и вывод номеров использованных правил будут происходить в следующем порядке: |
Proc_S(a,1) (символ ‘a’, вывод 1) Þ Proc_A(a,2) (‘c’, вывод 5) Þ Proc_C(a,3) (вывод 9) Þ Proc_A(a,3) (‘b’, вывод 4) Þ Proc_A(a,4) (‘a’, вывод 3, в Proc_C верн. i=5) Þ Proc_B(a,6) (‘b’, вывод 6, в Proc_C верн. i=7) Þ вернули 8=n+1 Þ stop(+). Правила вывода 1, 5, 9, 4, 3, 6.
S Þ(1) aA Þ(5) acC Þ(9) acAaBb Þ(4) acbAaBb Þ(3) acbaaBb Þ(6) acbaabb.
Метод рекурсивного спуска позволяет выбирать альтернативу на основе текущего символа входной цепочки. Если же имеется возможность обозревать не один, а несколько символов вперед от текущего положения входной головки, то область применения этого метода может быть расширена. Тогда можно искать подходящие правила на основе некоторого терминального символа, входящего в правую часть правила (т.е. этот символ не обязан быть первым). Выбор и в таком случае должен осуществляться однозначно, т.е. для каждого нетерминального символа левой части необходимо, чтобы в правой части разных правил не встречалось двух одинаковых терминальных символов.
Поскольку один и тот же терминальный символ может встречаться во входной цепочке неоднократно, то в зависимости от типа рекурсии требуется искать либо его крайнее левое, либо крайнее правое вхождение, т.е. метод требует анализа типа использованной в грамматике рекурсии.
Для применения метода рекурсивного спуска требуется осуществлять неформальный анализ правил исходной грамматики G. При наличии большого количества правил такой анализ практически нереализуем, поэтому данный метод хотя и нагляден, но слабо применим.
Существуют распознаватели, основанные на строго формализованном подходе. Подготовка данных (правил грамматики) может быть строго формализована и автоматизирована.
3.5.1.2 Разбор для LL(k)-грамматик
Логическим продолжением идеи рекурсивного спуска является попытка использовать для выбора единственной из множества альтернатив не один, а несколько символов входной цепочки. Сложность заключается в том, что эти несколько соседних символов цепочки могут быть получены с применением не одного, а нескольких правил.
Грамматика обладает свойством LL(k) (называется LL(k)-грамматикой) для k>0, если на каждом шаге вывода для однозначного выбора очередной альтернативы автомату с магазинной памятью необходимо знать один верхний символ стека и рассмотреть k символов входной цепочки справа от положения считывающей головки.
Существуют LL(1), LL(2), LL(3), … грамматики. Все они в совокупности образуют класс LL-грамматик. В этом обозначении (LL) первая L означает, что входная цепочка считывается в направлении слева направо, а вторая L – что выполняется левосторонний разбор. Число k показывает, сколько символов справа от считывающей головки нужно рассмотреть для однозначного выбора альтернативы.
Алгоритм разбора входных цепочек для LL(k)-грамматик называется k-предсказывающим алгоритмом.
Свойства LL(k)-грамматик.
§ Всякая LL(k)-грамматика для любого k>0 является однозначной.
§ Существует алгоритм проверки, является ли произвольная КС-грамматика LL(k)-грамматикой для строго определённого числа k.
§ Всякая грамматика, допускающая разбор по методу рекурсивного спуска, является LL(1)-грамматикой. Обратное не справедливо.
Проблемы LL(k)-грамматик:
§ Не существует алгоритма, позволяющего проверить, является ли произвольная КС-грамматика LL(k)-грамматикой для любого числа k.
§ Не существует алгоритма преобразования произвольной КС-грамматики к виду LL(k)-грамматики для некоторого k (либо доказывающего, что такое преобразование невозможно).
Для LL(k)-грамматик для любого k>1 не обязательно все k символов должны находиться в одной цепочке в правой части правила вывода. Если это так, то такая грамматика называется сильно LL(k)-грамматикой. Но обычно эти символы находятся в правых частях разных правил.
Особенности правил грамматики класса LL(1):
1) В правилах грамматики не может существовать для одного нетерминального символа двух или более правил с одинаковым первым терминальным символом в правой части.
2) В отличие от метода рекурсивного спуска допускаются правила вида A®Ba и пустые правила.
3) Правила грамматики не должны содержать левой рекурсии.
LL-грамматики позволяют построить распознаватели с линейной трудоемкостью.
Для построения распознавателей LL(k)-грамматик используются два специальных множества, определяемых следующим образом:
§ FIRST(k,a) – множество терминальных цепочек, выводимых из aÎ(VTÈVN)* и укороченных до k символов. Формально FIRST(k,a) ={wÎVT*½½w½£ k и aÞ*w или aÞ*wx, xÎ(VTÈVN)*}, aÎ(VTÈVN)*, k>0.
§ FOLLOW(k,A) – множество укороченных до k символов терминальных цепочек, которые могут непосредственно следовать за AÎVN в цепочках вывода. Формально: для AÎVN и k > 0 FOLLOW(k,A) ={wÎVT*½SÞ*aAg и wÎFIRST(k,g), aÎVT*}.
Очевидно, что, если имеется цепочка терминальных символов aÎVT*, то FIRST(k,a) – это первые k символов этой цепочки.
Доказано, что грамматика G(VT,VN,P,S) является LL(k)-грамматикой тогда и только тогда, когда выполняется условие: "(A®b)ÎR и "(A®g)ÎR, b¹g FIRST(k,bw)ÇFIRST(k,gw)=Æ для всех цепочек w таких, что SÞ*aAw.
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.