Восходящие методы синтаксического анализа Пример. Дана грамматика: S aABe , A Abc , A b, Bd . Терминальная цепочка: abbcde. Свертка цепочки: abbcde, aAbcde, aAde, aABe, S. Последовательность вывода: S S a A a A b b e d e B A a B c d e a A b c d e a b b c d e Грамматики предшествования Разбора грамматик простого предшествования – это анализ, между какими парами символов могут лежать левые и правые границы связки. В итоге это сводится к анализу следующих трёх бинарных отношений между символами (отношений предшествования): XY связка X Y XY связка X Y XY связка X Y 1). X Y – если X может стоять перед левой границей связки, начинающейся с Y (т.е. X может свернуться позже, чем Y); формально: X Y (A aXM R) : M Y . 2). X Y – если оба символа могут стоять рядом внутри связи, а потому могут свернуться одновременно; формально: X Y (A aXY R) . 3). X Y – если X может стоять последним в связке, а его сосед не принадлежит связке (т.е. X может свернуться раньше, чем Y); формально: X Y (A aMX R) : M Y . Вывод. Если для каждой пары символов грамматики существует не более одного отношения предшествования, то в любой промежуточной цепочке вывода границы связки определяются однозначно. Замечание. Для анализа отношений предшествования обычно в грамматику добавляется новый начальный нетерминал S', новый терминал #, и новую продукцию S ' # S # . Пример. Дана грамматика: S ' # S # , S a, S aT , S [S ] T b, T bT Пример вывода: S ' # S # #[ S ]# #[aT ]# #[abT ]# #[abb]# Рассмотрим отношение . Для этого просмотрим все правые части всех правил, в которых идёт терминал, а за ним нетерминал. Это правила: S ' # S # , S aT , S [S ] , T bT . Смотрим, на какие терминалы могут начаться строки, выводимые из соответствующих нетерминалов, т.е. анализируем их множества FIRST: FIRST (S) = { a, [ }. FIRST (T) = { b }. S T [ ] a b [ S T # # ] a b Рассмотрим отношение . Для этого просмотрим все правые части всех правил, в которых идёт нетерминал, а за ним терминал. Это правила: S ' # S # , S [S ] . Смотрим, какими символами (терминальными и нетерминальными) могут заканчиваться строки, выводимые из соответствующих нетерминалов (в нашем случае S), т.е. анализируем их множества LAST: LAST (S) = {a, T, b, [}. S T [ ] a b [ S T # # ] a b Например, для цепочки #[abb]# получаем следующие отношения предшествования: # [ a b b ] # . В данном случае, очевидно второе вхождение b является связкой, а потому её надо свернуть к T. Далее: # [ a b T ] # # [ a T ] # # [ S ] # # S # S Весь разбор реализуется с помощью стека, в котором хранится начало ещё не свёрнутой цепочки до текущего анализируемого символа. В стеке элементы находятся между собой в отношении , а самые верхние могут в отношении . Если между символами нет отношения, значит здесь ошибка. Определение. КС-грамматика называется грамматикой предшествования, если: 1). Она не содержит -правил. 2). Она не содержит разных правил с одинаковыми правыми частями. 3). Для каждой пары символов существует не более одного отношения предшествования. Замечание. В грамматике предшествования требуется, чтобы правые части всех правил были разные, иначе может быть не ясно, к какому нетерминалу надо сворачивать связку. Вывод. На практике метод мало применяется, т.к. очень мало грамматик можно впихнуть в рамки грамматик предшествования. Грамматики предшествования изучают больше из теоретических соображений, т.к. метод их разбора прост и ясен, но не более! LR(k)-грамматики LR(k)-грамматики – это наиболее широкий класс КС-грамматик, допускающих эффективный восходящий детерминированный разбор. Это наиболее распространённый класс грамматик, для которого строятся промышленные компиляторы. При поиске самой левой сворачиваемой связки, в отличие от грамматик предшествования, учитывается информация не только о парах соседних символах, но и о обо всей просмотренной слева до связки части входной цепочки. Определение. Ситуацией (помеченной продукцией) называется запись вида A ; , где большая точка указывает позицию (точку просмотра) внутри продукции A , ( и – две части продукции, возможно пустые), а – правый контекст длиной k. Текущее состояние LR(k)-анализатора обозначается множеством возможных ситуаций, которое конечно. Т.е. LR(k)-анализатор можно смоделировать конечным автоматом. LR(k)- анализатор LR(k) означает: • входная цепочка обрабатывается слева направо • выполняется правый вывод • не более k символов цепочки для принятия решения Доступные операции: • перенос ("shift") - Символы входной цепочки переносятся в магазин • свертка ("reduce") - Замена извлекаемой цепочки из магазина на нетерминал (в правой части – цепочка) Управляющая программа анализатора • Управляющая программа одинакова для всех LRанализаторов • Рассматривается пара: sm – текущее состояние на вершине магазина, ai – текущий входной символ; после этого вычисляется action [sm, ai]: 1. shift s, где s – состояние, 2. свертка по правилу A–>β, 3. допуск (accept) 4. ошибка. LR(0)-грамматики Пример. Пусть дана грамматика: S ' # S # , S aSc , S b . В начале разбора текущее состояние анализатора – это ситуация S ' # S # . После попадания в заключительное состояние автомата необходимо выполнить свёртку, а затем начать разбор новой свёрнутой цепочки этим же автоматом с самого начала цепочки. q0 S ' # S # # q1 q2 S ' #S # S S ' # S # S aSc S b q5 a S aSc S b # S b b q6 S S aS c S ' # S # q4 b S a Sc a q3 q7 c S aSc LR(k)-грамматики Пример. Пусть дана грамматика: S ' # S # , S aSc , S . Если мы попытаемся построить LR(0)-анализатор, то получим уже во втором состоянии конфликтную ситуацию: q0 S ' # S # q1 # S ' #S # S aSc S Здесь во втором состоянии не ясно, что нужно делать в зависимости от следующего символа, т.к. имеется ситуация S . Вывод. Эта грамматика не является LR(0)-грамматикой. Более того, любая грамматика с -правилами не является LR(0)грамматикой! Основная идея LR(k)-анализа заключается в добавлении к каждой ситуации правого контекста – следующих k символов, которые могут идти после нетерминала в левой части ситуации. Определение. Контекст – это цепочка длиной k из терминальный символов, в котором может находиться данная продукция A в текущем выводе. В начале разбора текущее состояние LR(k)-анализатора – это ситуация S ' # S # ;$ k . q0 S ' # S # ;$ # q1 q2 S ' #S # ;$ S S aSc; # S ' # S # ;$ S ; # q3 # a S ; c q7 c a S a Sc; c S a S aSc; c S ; c # c S ' # S #;$ $ [редукция 0] # [редукция 1] c [редукция 1] [редукция 2] q5 S a Sc; # S S aSc; c q4 S aS c; # q6 c S aSc; # [редукция 2] q8 q9 S aS c; c c S aSc; c [редукция 2] Главный недостаток LR(k)-анализаторов – это большая громоздкость. Например, LR(1)-анализатор для простого языка типа классического Pascal содержит многие тысячи состояний. SLR(k)-грамматики и LALR(k)грамматики Главный недостаток LR(k)-анализаторов – это громоздкость почти для всех практически важных языков. Идея заключается в том, чтобы анализ контекста выполняеть только в случае возникновения коллизий. Именно это и делает в SLR(k)-грамматиках (simple) и LALR(k)-грамматиках (look-ahead). Их общая идея: строится обычный LR(0)-анализатор, а в случае конфликтов начинают локально анализировать контекст. В SLR(k)-грамматиках при появлении ситуации A просто анализируется, какие терминальные цепочки длины k могут идти после A во всех возможных промежуточных цепочках вывода. В LALR(k)-анализируется контекст конфликтных ситуаций и предшествующие им ситуации. Множество LALR(k)-грамматик покрывает все SLR(k)-грамматики. Неоднозначные грамматики. Конфликты «перенос-свертка» Вопрос неоднозначности становится особенно важным в процессе построения управляющей таблицы анализатора LR(k)-языка, так как неоднозначность грамматики приводит к конфликтам при построении таблицы. Пример. Пусть дана грамматика G1, имеющая следующий набор правил: (1) stmt → if expr then stmt (2) stmt → if expr then stmt else stmt (3) stmt → other , где other мы используем для обозначения других операторов. Имеется следующая входная цепочка: if E1 then if E2 then S1 else S2. Неоднозначные грамматики. Конфликты «перенос-свертка» Имеется следующая входная цепочка: if E1 then if E2 then S1 else S2. Содержимое стека Необработанная часть входной цепочки Действие $ if E1 then if E2 then S1 else S2 shift $if E1 then if E2 then S1 else S2 shift $ if E1 then if E2 then S1 else S2 shift $ if E1 then if E2 then S1 else S2 shift $ if E1 then if E2 then S1 else S2 shift $ if E1 then if E2 then S1 else S2 shift $ if E1 then if E2 then S1 else S2 shift $ if E1 then if E2 then S1 else S2 shift Следующий шаг - две альтернативы: (а) применить свертку по правилу 1 к последовательности if E2 then S1 на вершине стека (б) перенести символ else на вершину стека. Разрешение конфликта перенос-свертка Вариант 1: (1) stmt → matched_stmt (2) stmt → unmatched_stmt (3) matched_stmt → if expr then matched_stmt else matched_stmt (4) matched_stmt → Other (5) unmatched_stmt → if expr then stmt (6) unmatched_stmt → if expr then matched_stmt else unmatched_stmt Новая грамматика порождает тот же язык, что и старая, но вывод цепочки if E1 then if E2 then S1 else S2 теперь не содержит конфликтов. Вариант 2: Соглашение, что в случае конфликта перенос-свертка, перенос приоритетен Содержимое стека Необработанная часть входной цепочки Действие $ if E1 then if E2 then S1 else S2 Shift $ if E1 then if E2 then S1 else reduce [2] $ if E1 then S1 reduce [1] $ Неоднозначные грамматики. Конфликт перенос-перенос Пример. Рассмотрим грамматику G2 ('id', '(', ')', '=' и ',' – терминалы). (1) stmt → id (parameters_list) (2) stmt → expr = expr (3) parameter_list → parameter_list, parameter (4) parameter_list → Parameter (5) parameter → id (6) expr → id (expr_list) (7) expr → id (8) expr_list → expr_list, expr (9) expr_list → Expr Содержимое стека Необработанная часть входной цепочки Действие $ id (id, id) shift $ id (id, id) shift $ id ( id, id) shift $ id (id , id) shift