Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) 1. ВВЕДЕНИЕ набрал Тейс Г., гр.2091 1 Раньше компилятор был частью транслятора. Сейчас компилятор и транслятор можно считать синонимами. Интерпретатор – программа, которая относится к типу программ «языковой процессор». На входе: программа на исходном языке На выходе: исполнение кода (вычислительный процесс) Транслятор – программа, которая относится к типу программ «языковой процессор». На входе: программа на исходном языке На выходе: программа на целевом (объектом) языке 2. УПРОЩЕННАЯ МОДЕЛЬ КОМПИЛЯТОРА Язык программирования – формальный язык для написания программ промежут. промежут. язык высокого лексический лексема синтаксич. представл. синтаксич. представл. уровня анализатор (токен) анализатор анализатор семантич. (атом) корректн. генератор кода объектн. язык таблица (словарь) Лексический анализатор (ЛА) На входе: набор символов (литер) Разбивает цепочку на слова (лексемы), где слово – некоторая структура, объединенная некой семантической структурой. «Умный» ЛА На входе транслитератор считывает первый символ и, в зависимости от него, определяет класс символа и что делать дальше. Примитивный ЛА – без транслитератора, не работает с классами слов Лексема – слово языка, которое выделяет ЛА. Атрибуты лексемы: Класс (константа, зарезервированное слово и т.д.) Значение В результате при лексическом анализе должны получить вот такую табличку: № строки Имя лексемы Класс Значение Синтаксический анализатор решает бинарную задачу: определение, соответствует ли последовательность лексем синтаксису языка. Правила построения корректных цепочек задаются грамматикой языка. Задачи синтаксического анализатора: должен обрабатывать ошибочные ситуации представить синтаксическую последовательность в виде атомов (единой конструкции) Семантический анализатор проверяет соответствие семантики входных выражений семантическим соглашениям языка, которые хранятся в таблице. Семантика – это смысл языковой конструкции. Генератор кода развертывает атомы в последовательность команд целевой машины (объектный язык). 3. ПРОХОДЫ КОМПИЛЯТОРОВ Лексический, синтаксический, семантический анализатор и генератор кода – фазы компиляции. Проход компилятора – просмотр кода от начала до конца. В зависимости от конструкции компилятора, фазы могут объединяться в проходы. Самый простой компилятор – однопроходный. Двухпроходный: 1 вариант: 1 проход – ЛА, СА, СМА; 2 проход – ГК. 2 вариант: 1 проход – ЛА; 2 проход – СА, СМА, ГК. Трехпроходный: 1 проход – ЛА; 2 проход – СА, СМА; 3 проход – ГК. Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) Принципы выбора проходности: 1. логика языка 2. оптимизация кода 3. экономия памяти набрал Тейс Г., гр.2091 2 4. ЛЕКСИЧЕСКИЙ АНАЛИЗАТОР Задачи ЛА Выделение лексем (токенов) Удаление комментариев Дополнительная обработка текста o Удаление лишних пробелов o Приведение к единому регистру Обработка ошибок o Превышение разрешенной длины слова o Незакрытый комментарий o Исправление ошибок o Обработка префиксов (“lo” вместо “load”) Выделение лексем (токенов) Токен Лексема Описание If If Зарезервированное слово Id A Идентификатор Op = Операция Op_add + Операция аддитивная Op_mul * Операция мультипликативная Num 2 Число Формирование таблицы a. таблица идентификаторов Имя № строки Тип А 10 b. таблица констант (чисел) Значение № строки 3.18 23 Классы лексем: зарезервированные слова операции идентификаторы константы (числа) служебные символы разделители _ ;()+* Обобщенная схема ЛА сканер транслитератор идентификатор классификатор Сканер o сканирует текст и заносит его в буфер o выделяет комментарии o приводит к одному регистру Транслитератор – возвращает класс входного символа Идентификатор – выделяет слово и определяет, где оно заканчивается Классификатор – распознает лексему и определяет ее класс Классификация лексем Пусть дан алфавит: {a, b, c, i, l, }, где - символ окончания цепочки (обобщенный символ разделителя) Задана грамматика: call, all, ill, ball, cab. Пусть ε – начальное состояние. Ошибочное состояние будем обозначать: 100 Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) Способ 1: Таблица переходов a b c i l ε ‘a’ ‘b’ ‘c’ ‘i' 100 ‘a’ 100 100 100 100 ‘al’ ‘al’ 100 100 100 100 ‘all’ ‘all’ 100 100 100 100 100 Распознали слово ‘c’ ‘ca’ 100 100 100 100 ‘ca’ 100 ‘cab’ 100 100 ‘cal’ ‘cab’ 100 100 100 100 100 Распознали слово ‘cal’ 100 100 100 ‘call’ 100 ‘call’ 100 100 100 100 100 Распознали слово … набрал Тейс Г., гр.2091 3 Можно сохранять только значащие переходы Достоинство: простота Недостатки: Неэффективное использование памяти Невозможность расширения Первый недостаток можно устранить, если хранить не таблицу переходов, а список. Способ 2: a ε b c i 100 10 l 100 11 l 100 Список переходов 10 12 20 100 30 20 a 40 100 21 l 11 100 22 l 12 100 23 100 "all" 21 22 23 "ball" Преимущество: меньшее использование памяти Способ 3: ε a b c i 100 Список переходов (более оптимальный вариант) 10 10 l 20 a 20 l l 30 "all" l "ball" 40 100 100 30 a b 100 31 "cab" 31 l l "call" 100 Преимущество: легко расширяемый автомат Недостаток: полезен только тогда, когда слова начинаются с разных букв Проблема: есть состояние, которое соответствует начальному состоянию, где перечислены все буквы, с которых могут начинаться слова. Арифметические операции К арифметическим операциям можно отнести и зарезервированные слова: div, not, and, xor, or. + - * / < > = <= >= <> Автомат: {<, >, =, } Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) < “<” 100 100 100 100 100 100 ε < <= <> > >= = 1 2 3 4 5 6 > “>” “>” 100 100 100 100 100 набрал Тейс Г., гр.2091 4 = “=” “=” 100 100 “=” 100 100 “<” “<=” “<>” “>” “>=” “=” 12 ε 2 7 3 8 5 9 1 4 11 6 10 Правила Обозначения: * - повторяется некоторое количество раз ? – может быть либо один раз, либо ни разу (для обозначения регулярной цепочки) ( )? и [ ] – эквивалентны 1. [+|-]digit* Примеры: +1, -1, 0, +10 digit ε +|- 2 digit 3 4 digit 2. num digit*(.digit*)?(E(+|-)?digit)? Пример: 10.875E-25 3. id letter((letter|digit)*)? // для идентификаторов 5. СИНТАКСИЧЕСКИЙ АНАЛИЗАТОР Грамматика – набор правил, который позволяет строить синтаксически верные конструкции. Классификация Хомского: 1. автоматная грамматика (A aB) 2. контекстно-свободная грамматика (КСГ) – нет зависимости от контекста 3. контекстно-зависимая грамматика (КЗГ) 4. свободная грамматика – без ограничений со свободным набором правил Знак – графическое начертание. Символ – знак, в который вложен смысл. Словарь – набор слов, из которых строятся конструкции языка. Терминальные символы (терминалы) – слова из словаря, из которых строятся цепочки (a,b,c). Нетерминальные символы – вспомогательные символы, требующиеся для построения правил грамматики (<A>,<B>,<C>). Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) набрал Тейс Г., гр.2091 5 Правило грамматики – выражение, в левой части которого всегда стоит нетерминальный символ, а в правой – цепочка из терминальных и нетерминальных символов, которые показывают, как стоятся синтаксически верные конструкции языка. <IF> if <логическое_выражение> then <блок> <A> a<B>b<C> Формы записи правил: 1. Синтаксические диаграммы терминал нетерминал 2. Нормальная форма Бэкуса-Наура (БНФ) if, else // терминал <оператор>, <блок> // нетерминал ::= // по определению есть | // или [] // необязательная часть () // повторение 3. Теоретическая форма <A> a<B><C> КСГ задается следующими множествами: 1. множество терминалов Lт 2. множество нетерминалов Lн (причем Lт в пересечении с Lн дает пустое множество) 3. множество правил (грамматик) <A> , где - цепочка из терминальных и нетерминальных символов ( может быть пустой цепочкой) 4. один нетерминал, помеченный как стартовый Пример 1. Грамматика: 1. <S> a<A><B>c 2. <S> ε 3. <A> c<S><A> 4. <A> <A>b 5. <B> b<B> 6. <B> a Цепочка: acabac Вывод: <S> a<A><B>c ac<S><A><B>c ac<A><B>c 1 3 2 Цепочка не является корректной, т.к. ее нельзя вывести из данной грамматики Пример 2. Грамматика: 1. <S> a<A><B>c 2. <S> ε 3. <A> c<S><B> 4. <A> <A>b 5. <B> b<B> 6. <B> a Цепочка: acabac Вывод: <S> a<A><B>c ac<S><B><B>c ac<B><B>c aca<B>c 1 3 2 6 5 acab<B>c acabac 5 6 Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) Цепочка является корректной в данной грамматике. набрал Тейс Г., гр.2091 6 Дерево – граф, у которого нет циклов и петель. Дерево синтаксического разбора: <S> 1 a<A><B>c 3 5 c<S><B> b<B> 2 6 6 ε a a Выписываем листья в порядке слева сверху вниз: acabac Можно получить и другое дерево: <S> a<A><B>c <A>b a c<S><B> ε a При его обходе получаем все ту же цепочку: acabac. оба дерева дают корректные цепочки, а это плохо, т.к. грамматика неоднозначна. Если, используя одну и ту же грамматику, корректная цепочка может быть распознана двумя различными способами (два дерева), то такая грамматика называется неоднозначной. Такая грамматика не может быть использована для построения синтаксиса языка программирования. 6. ЛИШНИЕ НЕТЕРМИНАЛЫ Пример 1. 1. <S> a<S>a 2. <S> b<A>d 3. <S> c 4. <A> c<B>d 5. <A> a<A>d 6. <B> d<A>f Проблема заключается в зацикливании между <A> и <B>. Бесплодные (мертвые) нетерминалы – нетерминалы, которые не порождают ни одной терминальной цепочки. Свойство 1. Если все символы правой части правила продуктивны, то продуктивны и символы в левой части. <S> - продуктивный («плодовитый») символ <A>,<B> - не продуктивные символы Алгоритм проверки на продуктивность: 1. Составляем список нетерминалов, для которых найдется хотя бы одно правило, правая часть которого не содержит нетерминальных символов 2. Если найдено такое правило, то в список продуктивных терминалов заносится нетерминал, который стоит в левой части 3. Проверяются те правила, у которых в правой части стоят терминальные символы и нетерминальные символы из списка продуктивных. Если найдено такое правило, то нетерминал, стоящий в левой части, заносится в список продуктивностей. 4. Повторяем эту операцию до тех пор, пока не будут проверены все правила грамматики. Если после проверки правил остались какие-то нетерминалы, которые не занесены в список продуктивных являются бесплодными (мертвыми). Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) Пример 2. 1. <S> a<S>b 2. <S> c 3. <A> b<S> 4. <A> a набрал Тейс Г., гр.2091 7 <A> является недостижимым терминалом. Нетерминал, который не появится ни в одной цепочке, которая выводится со стартового терминала называется недостижимым. Свойство 2. Если нетерминал в левой части правила является достижимым, то достижимы все символы в правой части. Алгоритм проверки на достижимость: 1. Образовать список достижимых терминалов, внеся в него стартовый терминал 2. Если найдено правило, левая часть которого уже имеется в списке, то включить в список нетерминалы, имеющиеся в правой части 3. Если на шаге 2 список не пополняется новыми нетерминалами, то получен список всех достижимых нетерминалов. Нетерминалы, не попавшие в него считаются недостижимыми 6. ТРАНСЛИРУЮЩИЕ ГРАММАТИКИ Лукасевич в 30-е гг. XX века предложил польскую запись (постфиксная и инфиксная форма записи). Постфиксная запись: (a+b)*c ab+c*; -a+b a-b+ Грамматика, позволяющая формировать выражения в польской записи: 1. <операнд> <операнд><операнд>+ 2. <операнд> <операнд><операнд>* 3. <операнд> I Грамматика перевода (арифметических выражений): 1. <E> <E>+<T>{+} 2. <E> <T> 3. <T> <T>*<P>{*} 4. <T> <P> 5. <P> (<E>) 6. <P> a{a} 7. <P> b{b} 8. <P> c{c} Символы, находящиеся в фигурных скобках { } называются символами действия. Они описывают процедуры, которые должны выполняться в этом месте. Нетерминальными символами здесь являются все символы, стоящие в < >. А терминальными: a,b,c,+,*. Сокращения: E – expression, T – term, P – prefix. Словарь: {+,*,(,),a,b,c} Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) Пример: ((a+b*c)+a) <E> набрал Тейс Г., гр.2091 8 <T> <P> (<E>) <E>+<T> {+} <T> <P> <P> a {a} (<E>) <E>+<T> {+} <T> <T>*<P> {*} <P> <P> с {c} a {a} b{b} При обходе символов действия получаем: {a}{b}{c}{*}{+}{a}{+} a bc* + a+ Вывод: перед нами транслирующая грамматика. Ее отличительная сторона: кроме терминальных и нетерминальных символов имеются символы действия. Проделанная операция называется переводом. С помощью грамматики мы можем произвести перевод. 8. АТРИБУТНЫЕ ТРАНСЛИРУЮЩИЕ ГРАММАТИКИ Атрибуты переменной: имя, значение, тип. 1 Синтезируемые атрибуты Грамматика (атрибутные правила): 1. <S> <E>p{res}r 2. <E>q <E>p+<T>r 3. <E>q <T>r 4. <T>q <T>p*<P>r 5. <T>q <P>r 6. <P>q (<E>r) 7. <P>q cr r p q p+r q r q p*r q r q r q r Терминальные символы: {+, *, (, ), } Проверим, принадлежит ли выражение c*(c+c) данной грамматике. Подставим в выражение значения 4*(6+2) <S> {res32} <E>32 <T>32 <T>4 * <P>8 <P>4 ( <E> )8 c4 <E>6 + <T>2 <T>6 <P>2 <P>6 c2 c6 В рассмотренном примере атрибуты были определены только у терминальных символов. Далее мы определяли атрибуты у нетерминальных символов, поднимаясь от листьев синтаксического дерева к его корню. Такая операция называется синтезом атрибутов. Значения переменных p,q,r – значения атрибутов (в данном Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) набрал Тейс Г., гр.2091 9 случае констант), которые были получены на этапе лексического анализа и записаны в таблицу констант. Итак, атрибуты есть как у терминальных, так и у нетерминальных символов. {res} – процедура, которая произведет операцию замены всего выражения на константу. Синтезируемыми атрибутами также могут быть метки. 2 Наследуемые атрибуты Грамматика: 1. <описание> type V <список переменных> 2. <список переменных> , V <список переменных> 3. <список переменных> ε Терминальные символы: {type, V, “,”, } Соответствует грамматике: type V,V,V Не соответствуют грамматике: V, type; type ,V; type V, Примеры: int a,b,c; char ch,chr Можно 1. 2. 3. получить атрибутивную грамматику: <описание> typet Vp {DT}t1,p1 <список переменных>t2 <список переменных>t , Vp {DT}t1,p1 <список переменных>t2 <список переменных>t ε (t1,t2) t; p1 p (t1,t2) t; p1 p Проверим, принадлежит ли выражение typereal V1,V2,V3 данной грамматике <описание> typereal V1 {DT}real,1 <список переменных>real , V2 {DT}real,2 <список переменных>real , V3 {DT}real,3 <список переменных>real ε В приведенном примере мы имели распространение атрибутов от корня дерева к его листьям (нисходящий метод распространения атрибутов). Этот метод называется наследованием атрибутов, а атрибуты, полученные таким образом, называются наследуемыми атрибутами. 9. НИСХОДЯЩИЕ МЕТОДЫ СИНТАКСИЧЕСКОГО РАЗБОРА 1 Метод МП-автомата (автомата с магазинной памятью) Автомат с МП – это математическая модель, с помощью которой мы можем решать задачи распознавания КСЕ. Автомат с МП: 1. Состояние (S) a. Сменить состояние (S’) 2. Магазин (синоним: стек) a. Втолкнуть в магазин (A) b. Вытолкнуть из магазина (берется верхний элемент) c. Оставить без изменения 3. Входная цепочка a. Сдвиг указателя b. Держать (не давать сдвигаться) Использовать состояния в дальнейшем мы не будем. МП-автомат определяется пятью элементами: 1. конечное множество входных символов (алфавит), дополненное знаком окончания цепочки 2. конечное множество магазинных символов, дополненное знаком окончания цепочки 3. конечное множество состояний, причем одно из состояний выделено как начальное 4. управляющий механизм, который каждой комбинации входного символа, магазинного символа и состояния ставит в соответствие либо выход, либо переход. Переход от выхода отличается тем, что при переходе выполняются операции над магазинным состоянием и входом. 5. начальное содержимое магазина Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) набрал Тейс Г., гр.2091 10. ИСПОЛЬЗОВАНИЕ АВТОМАТА ДЛЯ РАЗБОРА S-ГРАММАТИКИ 10 s-грамматика (simple, separated) – контекстно-свободная грамматика, для которой выполняются следующие условия: Правая часть каждого правила начинается с терминального символа Если два правила имеют одинаковые левые части, то правые части начинаются с различных терминальных символов <S> aL, где L – любая последовательность терминальных и нетерминальных символов Lr – любая последовательность терминальных и нетерминальных символов в реверсивном порядке Примеры некорректных для s-грамматики правил: <S> ε <S> <A>a <S> a<A>, <S> a<B> Грамматика: 1. <S> ab<A> 2. <S> b<A>b<S> 3. <A> a 4. <A> b<A> Входная цепочка: abba Шаг 1. В магазин заталкиваем стартовый нетерминал <S> Шаг 2. Маркер устанавливаем на “a” Шаг 3. Используем первое правило: <A> на дне магазина, “b” над <A> Магазин: b <S> <A> abba abba b <A> <A> <A> abba a abba Цепочка корректна. Пример некорректной цепочки: “ba”. Начинаем с использования правила 2. Магазин не останется пуст после разбора цепочки. Алфавит: {a,b, } Множество нетерминальных символов: {<S>,<A>, } Операция «заменить» - верхний символ заменяем на обратную цепочку: <S> Lr <S> <A> b #1: #2: #3: #4: a #1 #3 ошибка ошибка b #2 #4 ошибка вытолкнуть, сдвиг ошибка ошибка допустить ошибка Заменить (<A>b), Сдвиг Заменить (<S>b<A>), Сдвиг Вытолкнуть, Сдвиг Заменить(<A>), Сдвиг s-грамматика распознается с помощью МП-автомата с применением расширенной магазинной операции «Заменить». Аргументом этой операции является цепочка, обратная цепочке, остающейся после первого терминального символа s-правила. Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) набрал Тейс Г., гр.2091 11. ИСПОЛЬЗОВАНИЕ АВТОМАТА ДЛЯ РАЗБОРА Q-ГРАММАТИКИ 11 q-грамматика – контекстно-свободная грамматика (КСГ), для которой выполняются следующие условия: Правая часть каждого правила начинается либо с терминального символа, либо является пустой цепочкой Если два правила имеют одинаковые левые части, то множества выбора для этих правил не должны пересекаться (пересечения множеств выбора есть пустое множество) q мощнее, чем s Если в КСГ встречаются правила класса <A> ε, то для этого правила нужно найти функцию СЛЕД(<A>) во всех правилах грамматики, где встречается нетерминал <A>. Эта функция в результате дает множество, состоящее из терминальных символов и маркера окончания цепочки. Q-грамматика: 1. <S> a<A><S> 2. <S> b 3. <A> c<A><S> 4. <A> ε <A> ε {a} {b} {c} {a,b} СЛЕД(<A>) = {a,b} Множество выбора правила N – это множество терминальных символов, для которых корректно применение правила N. Входные символы: {a,b,c, } Магазинные символы: <S>, <A>, a b c #1 #2 отвергнуть #4 #4 #3 отвергнуть отвергнуть отвергнуть * - можно поступить двумя способами: Применить правило #4 Отвергнуть (выберем это) <S> <A> #1: #2: #3: #4: отвергнуть отвергнуть * допустить Заменить (<S>,<A>), Сдвиг Вытолкнуть, Сдвиг Заменить (<S>,<A>), Сдвиг Вытолкнуть, Держать Общие правила построения нисходящего транслятора с магазинной памятью: 1. Определяется множество входных символов (множество терминалов, дополненных маркером окончания цепочки) 2. Определяется множество магазинных символов (множество состоит из маркера дна, а также всех нетерминальных символов, кроме крайнего левого в правой части правила). 3. Изначально в магазин заталкивается маркер дна и начальный нетерминал 4. Строится управляющая таблица (строки в которой помечены магазинными символами, столбцы – входными символами) 5. Определяются правила грамматики, которые применяются для соответствующего входного символа и магазинного символа. Правила грамматики применяются тогда, когда магазинный символ является левой частью этого правила, а входной символ принадлежит его множеству выбора. a. Если правило имеет вид: <A> a , то пишем: Заменить( ), Сдвиг b. Если правило имеет вид: <A> ε, то пишем: Вытолкнуть, Держать c. Если имеется аннулирующее правило с нетерминалом <A> в его левой части, и элемент таблицы, соответствующий магазинному символу <A> и некоторому входному символу b не был создан, то пишем: Отвергнуть 6. Если терминал b является магазинным символом, то элементом таблицы, находящимся на пересечении столбца b и строки <B> будет: Вытолкнуть, Сдвиг 7. Элементом таблицы для столбца, помеченного концевым маркером и маркером дна, будет: Допустить 8. Если некий символ {x} является магазинным символом, то тогда для него во всей строчке будет: Выполнить(x), Вытолкнуть, Держать r Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) набрал Тейс Г., гр.2091 12 9. Если после предыдущих пунктов какие-то ячейки остались незаняты, то их нужно заполнить действием: Отвергнуть. 12. КОНТЕКСТНО-СВОБОДНАЯ ГРАММАТИКА КЛАССА LL(1) LL = Left Leftmost означает, что решение принимается по самому левому терминалу (1) означает, что решение принимается только по одному терминалу. LL(1)-грамматика: 1. <S> <A>b<B> 2. <S> d 3. <A> <C><A>b 4. <A> <B> 5. <B> c<S>d 6. <B> ε 7. <C> a 8. <C> ed {a,e,b,c} {d} {a,e} {c,b} {c} {d,b, } {a} {e} Продуктивные нетерминалы: <C>, <B>, <S>, <A> Достижимые нетерминалы: <S>, <A>, <B>, <C> 1. <A> 3 4 <C> <B> 7 8 4,6 4,5 a e b c ПЕРВ(<A>) = {a,e,b,c} b получаем таким образом: 1. пр.4: <A> <B> 2. пр.6: <B> ε 3. пр.1: <A> удаляется, остается b 3. ПЕРВ(<A>) = {a,e} 6. ПЕРВ(ε) = {пустое множество} СЛЕД(<B>) = {d,b, } d: <B> c<S>d c<A>b<B>d b: <S> <A>b<B> <B>b<B> : на <B> может закончиться ВЫБОР(пр.6) = ПЕРВ(ε) СЛЕД(<B>) 4. (такая ситуация не рекомендуется) ПЕРВ(<B>) = {c} СЛЕД(<A>) = {b} ВЫБОР(пр.4) = ПЕРВ(<B>) СЛЕД(<A>) Контекстно-свободная грамматика называется LL(1)-грамматикой тогда и только тогда, когда множества выбора правил с одинаковой левой частью не пересекаются. Входные символы: {a,b,c,d,e, } Магазинные символы: <S>, <A>, <B>, <C>, b, d, Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) <S> <A> <B> <C> b d #1: #2: #3: #4: #5: #6: #7: #8: a #1 #3 отвергнуть #7 отвергнуть отвергнуть отвергнуть b #1 #4 #6 отвергнуть вытолкнуть, сдвиг отвергнуть отвергнуть c #1 #4 #5 отвергнуть отвергнуть отвергнуть отвергнуть d #2 отвергнуть #6 отвергнуть отвергнуть вытолкнуть, сдвиг отвергнуть набрал Тейс Г., гр.2091 e #1 #3 отвергнуть #8 отвергнуть отвергнуть отвергнуть 13 отвергнуть отвергнуть #6 отвергнуть отвергнуть отвергнуть допустить Заменить(<B>b<A>), Держать Вытолкнуть, Сдвиг Заменить(b<A><C>), Держать Заменить(<B>), Держать Заменить(d<S>), Сдвиг Вытолкнуть, Держать Вытолкнуть, Сдвиг Заменить(d), Сдвиг 13. МЕТОД РЕКУРСИВНОГО СПУСКА Основная идея: каждому нетерминальному символу грамматики соответствует процедура, которая распознает все цепочки, порождаемые этими нетерминалами. Процедуры вызывают друг друга в процессе синтаксического разбора. Если в правой части стоит нетерминал, который стоит в левой части, то процедура разбора может вызывать сама себя. Пример: <A> <S>b<A> // хорошее правило <A> <A>b<S> // плохое правило – эффект левой рекурсии, может потенциально привести к зацикливанию Грамматика: 1. <S> a<A>{x}<S> 2. <S> b{z} 3. <A> c{y}<A><S>{V}b 4. <A> {w} {a} {b} {c} СЛЕД(<A>)={a,b} Символы действия, которые обрамляют первое действие в правиле, не будут заноситься в магазин. Пример: <S> {x}c{y}<A> - здесь {x} и {y} не попадут в магазин 3 нетерминала 3 процедуры: PM (главная процедура) 1. Вход=1-ая лексема цепочки 2. Вызов P<S> 3. Если Вход= , то Допустить иначе «Отвергнуть» P<S> 1. Если Вход=a, то вызов P1 2. Если Вход=b, то вызов P2 3. Если Вход=c, то «Отвергнуть» 4. Если Вход= , то «Отвергнуть» 5. P1: Сдвиг, Вызов P<A>, Выполнить(x), Вызов P<S>, Возврат 6. P2: Сдвиг, Выполнить(z), Возврат P<A> 1. Если Вход=a, то вызов P4 2. Если Вход=b, то вызов P4 3. Если Вход=c, то вызов P3 4. Если Вход= , то «Отвергнуть» 5. P3: Сдвиг, Выполнить(y), Вызов P<A>, Вызов P<S>, Выполнить(V), Проверить, Если Вход=b, то «Сдвиг» иначе «Отвергнуть», Возврат 6. P4: Выполнить(w), Возврат 14. ОБРАБОТКА ОШИБОК ПРИ НИСХОДЯЩЕМ РАЗБОРЕ Неправильная цепочка – синтаксически неверная входная цепочка. Любой компилятор обязан обнаруживать ошибки. Задачи: 1. Обнаружение ошибки Рассмотрим обработку ошибок при использовании магазинного автомата. Входная цепочка: aabca. Указатель стоит на «с». Выходим на ошибку тогда, когда в ячейке ошибки получаем «отвергнуть». За «отвергнуть» должна скрываться процедура PE (proceeding error) обработки ошибки. Выбор(<S>)={ }=L – множество выбора; c L <S> является ошибкой. Но мы знаем, что находится во множестве выбора. Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) набрал Тейс Г., гр.2091 Ошибка: «Ожидалось множество_выбора, но вместо встретилось с» Если указатель на конец цепочки, то ошибка: “<S>-выражение не завершено” “c стоит там, где ожидалось <S>-выражение” Пример: <S> a <S> (<S><R> <R> ,<S><R> <R> ) Это S-грамматика. Пример корректной цепочки: (a,a,(a,a)) Множество входных символов: a ( , ) a ( <S> #1 #2 <R> отвергнуть d отвергнуть e отвергнуть g отвергнуть h #1: Вытолкнуть, Сдвиг #2: Заменить (<R><S>), Сдвиг #3: Заменить (<R><S>), Сдвиг #4: Вытолкнуть, Сдвиг , отвергнуть a #3 отвергнуть i ) отвергнуть b #4 отвергнуть j 14 отвергнуть c отвергнуть f допустить 2. Описание ошибки Pa: Запятая стоит там, где ожидалось “a” или открывающая скобка Pb: Закрывающая скобка стоит там, где ожидалось “a” или открывающая скобка Pс: <S>-выражение не закончено: возможно пропущено “a” Pd: “a” стоит там, где ожидалась запятая или закрывающая скобка Pe: Открывающая скобка стоит там, где ожидалась запятая или закрывающая скобка Pf: <R>-выражение не закончено: возможно пропущена закрывающая скобка Pg: “a” встретилось после окончания <S>-правила (группа выражений) Ph: Открывающая скобка встретилась после окончания <S>-правила Pi: Запятая встретилась после окончания <S>-правила Pj: Возможно неправильно поставлены скобки 3. Нейтрализация ошибки – процесс изменения конфигурации синтаксического анализатора: на основе полученной ошибки, мы изменяем ход синтаксического разбора так, чтобы это могло привести к какому-то результату. Например, попробовать подставить точку с запятой при ее пропуске. Глобальная нейтрализация Главная цель: когда во входной цепочке встретилась ошибка, а синтаксический анализ необходимо продолжать, то во входной цепочке делается переход. Этот переход продолжается до тех пор, пока не будет найден входной символ из множества «надежных». И нейтрализация будет зависеть от этого входного символа и верхнего магазинного символа. «Надежные» символы бывают двух классов: Синхронизирующие символы – символы, относительно которых можно с высокой степенью вероятности возобновить процесс синтаксического анализа. К таким классам в Паскале, например, относят: “end.”, “;”, “)”, “then”. Начинающие символы Локальная нейтрализация 1. Генерация сообщения об ошибке 2. Втолкнуть ({ошибка}) 3. Втолкнуть (<S>) Если дальше снова пошли ошибки, возможно они являются следствием первой ошибки. В этом случае можно продолжить обработку дальше, либо закончить анализ, выдав сообщение об ошибке. Локальная нейтрализация Эвристика – способ отсеивания заведомо неверных путей. Применяется для сокращения времени выполнения. Идея метода: если обнаруживается какая-то локальная ошибка, то выдается сообщение, выполняются операции по нейтрализации ошибки (для каждого события разрабатывается набор Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) набрал Тейс Г., гр.2091 15 действий над входной цепочкой и магазином, которые нейтрализуют ошибку). После выполнения этих действий синтаксический разбор продолжается. После выдачи сообщений Pa и Pb: Вытолкнуть, Держать После выдачи сообщений Pd и Pe: Заменить(<R><S>), Держать После выдачи сообщений Pc и Pf: Вытолкнуть, Выход 15. ГЕНЕРАЦИЯ КОДА Разработаем свой ассемблер. A,B – операнды; R – результат (по сути, это регистры). Операции: 1. Арифметические операции ADD R A B // R = A + B SUB R A B // R = A - B MUL R A B // R = A * B DIV R A B // R = A div B 4. Логические операции CMP A B // сравнение JN <label> // A ≠ B JE <label> // A = B JA <label> // A > B JNA <label> // A >= B JL <label> // A < B JNL <label> // A <= B 5. Разное JMP <label> // переход ASG A B // A := B MOV A B // A B IN(A) OUT(A) Пример: for i:=1{GEN2} to N do begin read(A); S:=S+A;{GENadd} end; MOV I, 1 label1: CMP I, N JE label0 IN(A) ADD S, S, A ADD I, I, 1 JMP label1 label0: … Результат генерации кода – файл, содержащий последовательные строки промежуточного представления. Процедура GEN2 (Param1, Param2) Запись в файл MOV Param2, Param1 Процедура GENadd (Param1, Param2, Param3) Запись в файл ADD (Param3, Param1, Param2) 16. СЕМАНТИЧЕСКИЙ АНАЛИЗ 1. Статическая проверка (Static Checking) Выполняется на этапе компиляции. Типы проверок: a. Проверка типов: проверяется совместимость типов при выполнении операций и других действий. Самое сложное Эквивалентность по типу A: array[1..10] of integer B: array[1..10] of integer A и B эквивалентны. Но могут быть более сложные ситуации с собственными типами. Эквивалентность по структуре В FORTRAN можно присваивать массивы: A:=B Макаров В.А. «Теория языков программирования и методы трансляции» (курс лекций) набрал Тейс Г., гр.2091 16 Типы: Базовые (integer, real, char, Boolean) Выражение типа (пользовательские) – на основе базовых типов конструируются более сложные типы 1. Массив – конечное множество однотипных элементов – array[T,I] 2. Декартово произведение – многомерный массив TxT. Например, H: array[1..n] of array[1..k] of integer 3. Запись – конструкция, предполагающая объединение типов разного вида record((num x integer)(lex x array[T,I])) 4. Указатель – указатель на некоторую переменную, в которой хранится тип pointer(T) 5. Функция – позволяет от одних типов переходить к другим – F: T1xT2 T3 Например, Sin(x): real real b. Проверка управления: передача управления за пределы языковых конструкций (условие, цикл, конкатенация) должна производиться в место, которое является корректным с точки зрения языка. Например, в Си: break. c. Проверка единственности. Например, в case несколько пунктов с пересекающимися множествами d. Проверка имен. Например, проверка меток: каждая метка должна встретиться минимум 2 раза 2. Динамическая проверка (Dynamic Checking) Выполняется на этапе выполнения. 17. СТРУКТУРА КОМПИЛЯТОРОВ (для продвинутых) 1. FrontEnd a. лексический анализатор b. синтаксический анализатор 2. MiddleEnd a. семантический анализатор b. оптимизация 3. BackEnd – осуществляет генерацию кода a. выбор инструкций (кодогенерация) b. упорядочение инструкций c. дополнительная кодогенерация и распределение регистров d. получение файла ассемблера В распределении регистров наилучший вариант можно выбрать, только перебрав все варианты. Стратегия распределения регистров: вначале используется какой-то элементарный алгоритм если распределение не удается, используются более сложные алгоритмы 1-2 – аппаратно-независимая часть На переходе от FrontEnd к MiddleEnd – абстрактный синтаксис (дерево синтаксического разбора) На переходе от MiddleEnd к BackEnd – промежуточное представление 18. МЕТОДЫ ОПТИМИЗАЦИИ 1. Аппаратно-независимая оптимизация (выполняется на этапе MiddleEnd) a. Оптимизация цикла (инвариант цикла) – константу, не зависящую от цикла можно вынести и подсчитать вне цикла b. Линеаризация массивов – пересчет двумерного индекса в одномерный (со смещением) c. Удаление непродуктивного кода (неиспользуемых переменных и пр.) d. Оптимизация вызовов процедур e. Замена операций i. Умножение на степень двойки можно заменить операцией сдвига ii. Возведение в квадрат заменяем на перемножение друг на друга 2. Аппаратно-зависимая оптимизация (выполняется на этапе BackEnd) Необходимо знание ассемблера – знание команд целевой машины a. Выбор оптимальных инструкций b. Изменение порядка инструкций c. Разбиение на линейные участки Линейный участок – последовательность кода с одной точкой входа и одной точкой выхода