CodeNet / Приложения / Алгоритмы / Разбор выражений. Компиляторы и интерпретаторы. / Основы компиляции
LR(k)-грамматики.
SLR(1), LALR(1) - грамматики. Если в процессе LR-разбора принять детерминированное решение о сдвиге/свертке удается, рассматривая только цепочку x и пер- вые k символов непросмотренной части входной цепочки u (эти k символов называют аванцепочкой), говорят, что грамматика обла- дает LR(k)-свойством. -S-- / \ /-x¬ \ --w-+--u-- Различие между LL(k)- и LR(k)-грамматиками в терминах дерева вывода: -S- / ¦ \ / A \ / / \ \ -w-+-v-+-u- В случае LL(k)-грамматик однозначно определить правило, приме- ненное к A, можно по w и первым k символам vu, а в случае LR(k)-грамматик - по w,v и первым k символам u. Это нестрогое рассуждение показывает, что LL(k)-языки < LR(k)-языки (опро- вергните его для k=0). LR(0)-грамматики Рассмотрим вначале наиболее простые грамматики этого класса - LR(0). При разборе строки LR(0)-языка можно вообще не исполь- зовать аванцепочку - выбор между сдвигом и сверткой делается на основании цепочки x (см.картинку). Так как в процессе разбора она изменяется только с правого конца, ее называют стеком. Бу- дем считать, что в грамматике нет бесполезных символов и на- чальный символ не встречается в правых частях правил - тогда свертка к начальному символу сигнализирует об успешном заверше- нии разбора. Попробуем описать множество цепочек из терминалов и нетерминалов, появляющихся в стеке в процессе всех LR-разбо- ров (другими словами - всех правых выводов из грамматики). Определим следующие множества: L(A:v) - левый контекст правила A:v - множество состояний стека, непосредственно перед сверткой v в A в ходе всех успеш- ных LR-разборов. Очевидно, каждая цепочка из L(A:v) кончается на v. Если у всех таких цепочек отрезать хвост v, то получится множество цепочек, встречающихся слева от A в процессе всех ус- пешных правых выводов. Обозначим это множество L(A) - левый контекст нетерминала A. Упражнение: показать, что данное определение корректно, т.е. множество L(A) не зави- сит от выбора правила A:v. Построим грамматику для множества L(A). Терминалами новой грамматики будут терминалы и нетерминалы исходной грамматики, нетерминалы новой грамматики обозначим <L(A)>,... - их значени- ями будут левые контексты нетерминалов исходной грамматики. Ес- ли S - начальный символ исходной грамматики, то грамматика ле- вых контекстов будет содержать правило <L(S)> : e - левый контекст S содержит пустую цепочку Для каждого правила исходной грамматики, например, A : B C d E добавим в новую грамматику правила: <L(B)> : <L(A)> - L(B) включает L(A) <L(C)> : <L(A)> B - L(C) включает L(A) B <L(E)> : <L(A)> B C d - L(E) включает L(A) B C d Упражнение: показать, что L(A) не содержит других цепочек, кро- ме выводимых из грамматики. Полученная грамматика имеет специальный вид (такие граммати- ки называются леволинейными), следовательно, множества левых контекстов - регулярны. Из этого следует, что принадлежность цепочки левому контексту какого-либо нетерминала можно вычис- лять индуктивно с помощью конечного автомата, просматривая це- почку слева направо. Опишем этот процесс конструктивно. Назовем LR(0)-ситуацией правило грамматики с одной отмечен- ной позицией между символами правой части правила. Например, для грамматики S:A ; A:aAA ; A:b существуют следующие LR(0)-си- туации: S:_A; S:A_; A:_aAA; A:a_AA; A:aA_A; A:aAA_; A:_b; A:b_. (позиция обозначена символом подчеркивания). Будем говорить, что цепочка x согласована с ситуацией А:b_c, если x=ab и a принадлежит L(A). (Другими словами, LR-вывод мо- жет быть продолжен x_... = ab_... :: abc_... :: aA_... :: S_ .) В этих терминах L(A:b) - множество цепочек, согласованных с си- туацией A:b_, L(A) - цепочки, согласованные с ситуацией A:_b, для любого правила A:b. Пусть V(u) - множество ситуаций, согласованных с u. Покажем, что функция V - индуктивна. Если в множество V(u) входит ситуация A:b_cd, то ситуация A:bc_d принадлежит V(uc). (c - терминал или нетерминал; b, d - последовательности (может быть пустые) терминалов и нетермина- лов). Других ситуаций вида A:b_d, с непустым b в V(uc) нет. Ос- талось добавить в V(uc) ситуации вида C:_..., для каждого не- терминала C, левый контекст которого содержит uc. Если ситуация A:..._C... (C-нетерминал) принадлежит множеству V(uc), то uc принадлежит L(C) и V(uc) включает в себя ситуации вида C:_... для всех C-правил грамматики. Упражнение: показать, что других ситуаций V(uc) не содержит. V(e) содержит ситуации S:_... (S-начальный символ), а также ситуации A:_..., если нетерминал A встречается непосредственно после _ в ситуациях, уже включенных в V(e). Пример: построим функцию V для грамматики S:A; A:aAA; A:b. 0 V(e) = { S:_A; A:_aAA, A:_b } 1 V(A) = { S:A_ } 2 V(a) = { A:a_AA; A:_aAA, A:_b } V(aa)=V(a); V(ab)=V(b); 3 V(b) = { A:b_ } 4 V(aA) = { A:aA_A; A:_aAA, A:_b } V(aAa)=V(a);V(aAb)=V(b); 5 V(aAA) = { A:aAA_ } Наконец, мы готовы дать определение LR(0)-грамматики. Пусть u - содержимое стека в процессе LR-разбора, V(u)-множество LR(0) ситуаций, согласованных с u. Если V(u) содержит ситуацию вида А:x_ (x-последовательность терминалов и нетерминалов), то u принадлежит L(A:x) и допустима свертка x в A. Если V(u) со- держит ситуацию A:..._a... (а-терминал), то допустим сдвиг. Го- ворят о конфликте сдвиг-свертка, если для одной цепочки u до- пустимы и сдвиг, и свертка. Говорят о конфликте свертка- свертка, если допустимы свертки по различным правилам. Грамматика называется LR(0), если для всех состояний стека в процессе LR-вывода нет конфликтов сдвиг-свертка или свертка-свертка. Рассмотренная выше грамматика является LR(0)-грамматикой. Ее функция V принимает 6 различных значений (вычисляется конечным автоматом с 6 состояниями). В состояниях 0,2,4 возможен только сдвиг, в состоянии 3 - свертка по правилу A:b, в состоянии 5 - свертка A:aAA, в состоянии 1 - свертка S:A - т.е. успешное за- вершение разбора. Остается показать, как можно построить парсер, разбирающий предложения LR(0)-языка. Чтобы не вычислять значение функции V заново для каждого нового состояния стека, будем хранить в сте- ке вместе с каждым символом xi значение V на цепочке (x0...xi). стек.сделать пустым стек.добавить ( '#', начальное состояние ) цикл . выбор из V ( стек.вершина.состояние ).действие . . "сдвиг" => . . прочитать очередной символ в ( новый символ ) . . "свертка" => . . удалить правую часть правила из стека . . новый символ := левая часть правила . . "успех" => . . стоп( "успех" ) . конец выбора . старое состояние := V ( стек.вершина.состояние ) . новое состояние := старое состояние . переход [новый символ] . если новое состояние = "Ошибка" то стоп( "ошибка" ) кесли . стек.добавить ( новый символ, новое состояние ) конец цикла Таблицы LR(0)-парсера для грамматики 1) S:A; 2) A:aAA; 3) A:b. --T------T-------------T----------------------¬ ¦ ¦ Пре¦ ¦ Действие ¦ Переход ¦ ¦ ¦ фикс ¦ ¦ A a b ¦ +-+------+-------------+----------------------+ ¦0¦ e ¦ сдвиг ¦ 1 2 3 ¦ ¦1¦ A ¦ успех ¦ Ошибка Ошибка Ошибка ¦ ¦2¦ a ¦ сдвиг ¦ 4 2 3 ¦ ¦3¦ b ¦ свертка 3,А ¦ Ошибка Ошибка Ошибка ¦ ¦4¦ aA ¦ сдвиг ¦ 5 2 3 ¦ ¦5¦ aAA ¦ свертка 2,А ¦ Ошибка Ошибка Ошибка ¦ L-+------+-------------+----------------------- LR(k)-грамматики Для выбора между сдвигом или сверткой в LR(0) разборе ис- пользуется только состояние стека. В LR(k) разборе учитывается также k-первых символов непросмотренной части входной цепочки (так называемая аванцепочка). Для обоснования метода следует аккуратно повторить рассуждения предыдущего параграфа, внеся изменения в определения. Будем включать в левый контекст правила также аванцепочку. Если в правом выводе применяется вывод wAu : wvu, то пара {wv,FIRSTk(u)} принадлежит Lk(A:v), а пара {w,FIRSTk(u)} - Lk(A). Множество левых контекстов, как и в случае LR(0), можно вычислять с помощью индукции по левой цепочке. Назовем LR(k)-ситуацией пару: правило грамматики с отмеченной позицией и аванцепочку длины не более k. Отделять правило от аванцепочки будем вертикальной чертой. Будем говорить, что цепочка x согласована с ситуацией А:b_c|t если существует LR-вывод: x_yz = ab_yz :: abc_z :: aA_z :: S_, и FIRSTk(z)=t. Правила индуктивного вычисления множества состояний Vk следующие: Vk(e) содержит ситуации S:_a|e для всех правил S:a, где S-начальный символ. Для каждой ситуации А:_Ba|u из Vk(e), каж- дого правила B:b и цепочки x, принадлежащей FIRSTk(au), надо добавить в Vk(e) ситуацию B:_b|x. Если в Vк(w) входит ситуация A:b_cd|u, то ситуация A:bc_d|u будет принадлежать Vk(wc). Для каждой ситуации А:b_Cd|u из Vk(wc), каждого правила C:f и цепочки x, принадлежащей FIRSTk(du) надо добавить в Vk(wc) ситуацию C:_f|x. Пример: построим функцию V1 для грамматики S:A; A:AaAb|e. 0 V1(e) = { S:_A|e; A:_AaAb|e,a, A:_|e,a } 1 V1(A) = { S:A_|e, A:A_aAb|e,a } 2 V1(Aa) = { A:Aa_Ab|e,a; A:_AaAb|a,b, A:_|a,b } 3 V1(AaA) = { A:AaA_b|e,a, A:A_aAb|a,b } 4 V1(AaAa) = { A:Aa_Ab|a,b; A:_AaAb|a,b, A:_|a,b } 5 V1(AaAb) = { A:AaAb_|e,a } 6 V1(AaAaA) = { A:AaA_b|a,b A:A_aAb|a,b } V1(AaAaAa)=V1(AaAa) 7 V1(AaAaAb)= { A:AaAb_|a,b } ( A:_AaAb|e,a - сокращенная запись двух LR(1)-ситуаций: A:_AaAb|e и A:_AaAb|а ) Используем построенные множества LR(k)-состояний для разре- шения вопроса сдвиг-свертка. Пусть u - содержимое стека, а x - аванцепочка. Очевидно, что свертка по правилу A:b может быть проведена, если Vk(u) содержит ситуацию A:b_|x. Решение вопроса о допустимости сдвига требует аккуратности, если в грамматике имеются e-правила. В ситуации A:b_c|t (c не пусто) сдвиг возмо- жен, если c начинается с терминала и x принадлежит FIRSTk(ct). Неформально говоря, можно занести в стек самый левый символ правой части правила, подготавливая последующую свертку. Если c начинается с нетерминала (ситуация имеет вид A:b_Cd|t), то за- нести в стек символ, подготавливая свертку в C, можно только в случае, если C не порождает пустую цепочку. Например, в состо- янии V(e)={ S:_A|e; A:_AaAb|e,a, A:_|e,a } нет допустимых сдви- гов, т.к. при выводе из A терминальных цепочек на некотором ша- ге требуется применить правило A:e к нетерминалу A, находящему- ся на левом конце цепочки. Определим множество EFFk(x), состоящее из всех элементов множества FIRSTk(x), при выводе которых нетерминал на левом конце x (если он есть) не заменяется на пустую цепочку. В этих терминах сдвиг допустим, если в множестве Vk(u) есть ситуация А:b_c|t, c не пусто и x принадлежит EFFk(ct). Грамматика называется LR(k)-грамматикой, если ни одно LR(k) состояние не содержит двух ситуаций A:b_|u и B:c_d|v, таких что u принадлежит EFFk(dv). Такая пара соответствует конфликту свертка-свертка, если d пусто, и конфликту сдвиг-свертка, если d не пусто. LR(k)-парсер устроен аналогично LR(0). Действие из множества {сдвиг, свертка, успех, ошибка}, выполняемое на очередном шаге LR-разбора, есть функция от состояния стека Vk(u) и аванцепочки x: сдвиг, если A:b_c|t содержится в Vk(u), c!=e, x из EFF(ct); свертка, если A:a_|x содержится в Vk(u); успех, если S:A|e содержится в Vk(u); ошибка в остальных случаях. Таблицы LR(1)-парсера для грамматики S:A; A:AaAb|e. --T-------T----------------------T---------------------¬ ¦ ¦Префикс¦ Действие ¦ Переход ¦ ¦ ¦ ¦ a b e ¦ a b А ¦ +-+-------+----------------------+---------------------+ ¦0¦ e ¦ Св.0,А Ошибка Св.0,А ¦ Ошибка Ошибка 1 ¦ ¦1¦ A ¦ Сдвиг Ошибка Успех ¦ 2 Ошибка Ошибка¦ ¦2¦ Aa ¦ Св.0,А Св.0,А Ошибка ¦ Ошибка Ошибка 3 ¦ ¦3¦ AaA ¦ Сдвиг Сдвиг Ошибка ¦ 4 5 Ошибка¦ ¦4¦ AaAa ¦ Св.0,А Св.0,А Ошибка ¦ Ошибка Ошибка 6 ¦ ¦5¦ AaAb ¦ Св.4,А Ошибка Св.4,А ¦ Ошибка Ошибка Ошибка¦ ¦6¦ AaAaA ¦ Сдвиг Сдвиг Ошибка ¦ 4 7 Ошибка¦ ¦7¦ AaAaAb¦ Св.4,А Св.4,А Ошибка ¦ Ошибка Ошибка Ошибка¦ L-+-------+----------------------+---------------------- Упражнение: запрограммируйте интерпретатор LR(k)-таблиц. На практике LR(k)-грамматики при k>1 не применяются. На это имеются две причины. Первая: очень большое число LR(k) состо- яний. Вторая: для любого языка, определяемого LR(k)-граммати- кой, существует LR(1)-грамматика; более того, для любого детер- минированного КС-языка существует LR(1)-грамматика. Число LR(1)-состояний для практически интересных грамматик также весьма велико. LR(0) свойством такие грамматики обладают редко. На практике чаще всего используются промежуточные между LR(0) и LR(1) методы, известные под названиями SLR(1) и LALR(1). SLR(1) и LALR(1) грамматики. В основе этих двух методов лежит одна и та же идея. Построим множество канонических LR(0)-состояний грамматики. Если это множество не содержит конфликтов, то можно применить LR(0)-пар- сер. Иначе попытаемся разрешить возникшие конфликты, рассматри- вая односимвольную аванцепочку. Другими словами, попробуем построить LR(1) парсер с множеством LR(0)-состояний. В SLR(1) грамматиках (Simple LR(1) - простых LR(1)-граммати- ках) для разрешения конфликтов используется множество FOLLOW(X) - множество терминалов, встречающихся после X. Если в состоянии имеется ситуация A:b_, свертка допускается, если только аванце- почка принадлежит FOLLOW(A). Грамматика является SLR(1)-грамматикой, если для двух любых LR(0) ситуаций из одного состояния A:a_b и B:c_d выполняется одно из следующих условий: - b!=e, d!=e (конфликта нет, требуется сдвиг); - b=d=e и FOLLOW(A) не пересекается с FOLLOW(B) (конфликт "свертка/свертка" может быть устранен с учетом аванце- почки); - b=e, d<>e и FOLLOW(A) не пересекается с EFF(tFOLLOW(B)) (конфликт "сдвиг/свертка" может быть устранен с учетом аванцепочки). Построим LR(0)-состояния для грамматики арифметических фор- мул: S:E; E:E+T|T; T:T*F|F; F:(E)|a. 0 V(e) ={ S:_E; E:_E+T, E:_T, T:_T*F, T:_F, F_a, F:_(E) } 1 V(E) ={ S:E_, E:E_+T } 2 V(T) ={ E:T_, T:T_*F, 3 V(F) ={ T:F_ } 4 V(a) ={ F:a_ } 5 V(() ={ F:(_E); E:_E+T, E:_T, T:_T*F, T:_F, F_a, F:_(E) } 6 V(E+) ={ E:E+_T; T:_T*F, T:_F, F_a, F:_(E) } 7 V(T*) ={ T:T*_F; F:_a, F:_(E) } 8 V((E) ={ F:(E_), E:E_+T } (T=T; (F=F; (a=a; ((=( 9 V(E+T)={ E:E+T_, T:T_*F } E+F=F; E+a=a; E+(=( 10 V(T*F)={ T:T*F_ } T*a=a; T*(=( 11 V((E))={ F:(E)_ } (Е+=Е+; Е+Т*=Т* LR(0)-конфликты возникают в состояниях 1,2,9, но 1: FOLLOW(S) = {е}, FIRST(+T) = {+} 2: FOLLOW(E) = {+,),e} FIRST(*F) = {*} 9: FOLLOW(E) = {+,),e} FIRST(*F) = {*}, следовательно, конфликты разрешаются с использованием SLR(1) техники и грамматика является SLR(1)-грамматикой. Таблицы SLR(1)-парсера для грамматики арифметических формул ---T-------------------------T--------------------------------¬ ¦ ¦ Действие ¦ Переход ¦ ¦ ¦ e + * а ( ) ¦ + * а ( ) E T F ¦ +--+-------------------------+--------------------------------+ ¦0 ¦ Ош Ош Ош Сдв Сдв Ош ¦ Ош Ош 4 5 Ош 1 2 3 ¦ ¦1 ¦ Усп Сдв Ош Ош Ош Ош ¦ 6 Ош Ош Ош Ош Ош Ош Ош ¦ ¦2 ¦ 1,E 1,E Сдв Ош Ош 1,E ¦ Ош 7 Ош Ош Ош Ош Ош Ош ¦ ¦3 ¦ 1,T 1,T 1,T Ош Ош 1,T ¦ Ош Ош Ош Ош Ош Ош Ош Ош ¦ ¦4 ¦ 1,F 1,F 1,F Ош Ош 1,F ¦ Ош Ош Ош Ош Ош Ош Ош Ош ¦ ¦5 ¦ Ош Ош Ош Сдв Сдв Ош ¦ Ош Ош 4 5 Ош 8 2 3 ¦ ¦6 ¦ Ош Ош Ош Сдв Сдв Ош ¦ Ош Ош 4 5 Ош Ош 9 3 ¦ ¦7 ¦ Ош Ош Ош Сдв Сдв Ош ¦ Ош Ош 4 5 Ош Ош Ош 10 ¦ ¦8 ¦ Ош Сдв Ош Ош Ош Сдв ¦ 6 Ош Ош Ош 11 Ош Ош Ош ¦ ¦9 ¦ 3,Е 3,Е Ош Ош Ош 3,Е ¦ Ош 7 Ош Ош Ош Ош Ош Ош ¦ ¦10¦ 3,Т 3,Т 3,Т Ош Ош 3,Т ¦ Ош Ош Ош Ош Ош Ош Ош Ош ¦ ¦11¦ 3,F 3,F 3,F Ош Ош 3,F ¦ Ош Ош Ош Ош Ош Ош Ош Ош ¦ L--+-------------------------+--------------------------------- LALR(1)-метод (Look Ahead - заглядывание вперед) заключается в следущем. Введем на множестве LR(1)-ситуаций отношение экви- валентности: будем считать две ситуации эквивалентными, если они различаются только аванцепочками. Например, ситуации A:Aa_Ab|e и A:Aa_Ab|a эквивалентны. Построим каноническое мно- жество LR(1)-состояний и объединим состояния, состоящие из мно- жества эквивалентных ситуаций. Например, LR(1) состояния 2 и 4 грамматики S:A; A:AaAb|e эк- вивалентны: 2 V1(Aa) = { A:Aa_Ab|e,a; A:_AaAb|a,b, A:_|a,b } 4 V1(AaAa) = { A:Aa_Ab|a,b; A:_AaAb|a,b, A:_|a,b } и могут быть объединены в одно состояние 2+4 V1(Aa) = { A:Aa_Ab|e,a,b; A:_AaAb|a,b, A:_|a,b } Также эквивалентны состояния 3,6 и 5,7 этой грамматики. Если полученное множество состояний не содержит LR(1) конфликтов, и, следовательно, позволяет построить LR(1)-парсер, то говорят, что грамматика обладает свойством LALR(1). Например, грамматика S:A; A:AaAb|e является LALR(1), Таблицы LALR(1)-парсера для грамматики S:A; A:AaAb|e. ----T-------T----------------------T---------------------¬ ¦ ¦Префикс¦ Действие ¦ Переход ¦ ¦ ¦ ¦ a b e ¦ a b А ¦ +---+-------+----------------------+---------------------+ ¦ 0¦ e ¦ Св.0,А Ошибка Св.0,А ¦ Ошибка Ошибка 1 ¦ ¦ 1¦ A ¦ Сдвиг Ошибка Успех ¦ 2 Ошибка Ошибка¦ ¦2+4¦ Aa ¦ Св.0,А Св.0,А Ошибка ¦ Ошибка Ошибка 3+6 ¦ ¦3+6¦ AaA ¦ Сдвиг Сдвиг Ошибка ¦ 4 5+7 Ошибка¦ ¦5+7¦ AaAb ¦ Св.4,А Св.4,А Св.4,А ¦ Ошибка Ошибка Ошибка¦ L---+-------+----------------------+---------------------- Заметим, что при слиянии канонических LR(1)-состояний, раз- личающихся только аванцепочками, получается множество канони- ческих LR(0)-состояний, для каждой ситуации которого вычислено множество допустимых аванцепочек (Докажите!). Следовательно, SLR(1) и LALR(1) методы при успешном применении дают одинаковые таблицы парсера. Метод LALR(1) применим к более широкому классу грамматик, чем SLR(1). Это объясняется тем, что отношение FOLLOW(A), применяемое при вычислении допустимых аванцепочек в SLR(1)-методе не использует всю доступную информацию - оно не зависит от левого контекста правила A:... Действительно, су- ществуют LALR(1)- грамматики, не принадлежащие классу SLR(1). Упражнение: покажите, что грамматика является LALR(1), но не SLR(1): S:L=R|R; L:*R|a; R:L Упражнение: к какому типу принадлежат следующие грамматики? 1) S:Aa|dAb|cb|dca; A:c; 2) S:Aa|dAb|Bb|dBa; A:c; B:c 3) S:Aa|dAb|Bb|dBa|c; A:c; B:c