CodeNet / Приложения / Алгоритмы / Разбор выражений. Компиляторы и интерпретаторы. / Основы компиляции
Языки и грамматики. Простейший компилятор.
Алфавит - это любое множество символов. Понятие символа не определяется. Цепочка символов 0,1,2 записывается как "012" (или 012). Другие обозначения: R x - цепочка x с символами в обратном порядке n x - цепочка x, повторенная n раз x* - цепочка x, повторенная 0 или более раз x+ - цепочка x, повторенная 1 или более раз xy - сцепление (конкатенация) цепочек x и y |x| - длина (число символов) цепочки x e - пустая цепочка Цепочку из одного символа будем обозначать самим символом. Буквы x,y,z,u,v,w,t будем применять для обозначения цепочек. Множество всех цепочек из элементов множества E естественно обозначить через E*. Язык - это подмножество E*. Примеры язы- n n ков: Си, русский, { 0 1 | n >= 0 }. Язык можно задать: - перечислив все цепочки; - написав программу-распознаватель, которая получает на вход цепочку символов и выдает ответ "да", если цепочка принадлежит языку и "нет" в противном случае; - с помощью механизма порождения - грамматики. Чтобы задать грамматику, требуется указать: - множество символов алфавита (или терминальных символов) E. Будем обозначать их строчными символами алфавита и цифрами; - множество нетерминальных символов (или метасимволов), не пе- ресекающееся с E со специально выделенным начальным символом S. Будем обозначать их прописными буквами; - множество правил вывода, определяющих правила подстановки для цепочек. Каждое правило состоит из двух цепочек (напри- мер, x и y), причем x должна содержать по крайней мере один нетерминал; и означает, что цепочку x в процессе вывода мож- но заменить на y. Вывод цепочек языка начинается с нетерми- нала S. Правило грамматики будем записывать в виде x : y. (Также употребляется запись x ::= y или x -> y) Более строго, определим понятие выводимой цепочки: - S - выводимая цепочка; - если xyz - выводимая цепочка и в грамматике имеется правило y:t, то xtz - выводимая цепочка; - определяемый грамматикой язык состоит из выводимых цепочек, содержащих только терминальные символы. Примеры: а) S : e б) S : e S : 0S1 S : (S) S : SS Для сокращения записи принято использовать символ "или" - "|". Короткая форма записи предыдущих примеров: а) S : e | 0S1 б) S : e | (S) | SS Более сложный пример: в) S : aSBC | abC CB : BC bB : bb cC : cc bC : bc n n n Эта грамматика порождает язык a b c (доказательство этого факта - упражнение). Грамматики в свою очередь образуют т.н. метаязык. Выше была описана "академическая" форма записи метаязыка. На практике применяется также другая форма записи, традиционно называемая нормальными формами Бэкуса-Наура (НФБН). Терминалы в НФБН запи- сываются как обычные символы алфавита, а нетерминалы - как име- на в угловых скобках <>. Например, грамматику целых чисел без знака можно записать в виде: <число> : <цифра> | <цифра> <число> <цифра> : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Лирическое отступление. Можно ли записать грамматику какого-ли- бо метаязыка на нем самом? Ответ - да. Решение - упражнение. Рассмотрим язык простейших арифметических формул: <формула> : (<формула>) | <число> | <формула><знак><формула> <знак> : + | * Почему "3+5*2" является формулой? Приведем последовательность преобразований цепочек (так называемый "разбор" или "вывод"): <формула> : <формула> <знак><формула> : <формула><знак><формула><знак><формула> : <число> <знак><формула><знак><формула> : 3 <знак><формула><знак><формула> : 3 + <формула><знак><формула> : 3 + <число> <знак><формула> : 3 + 5 <знак><формула> : 3 + 5 * <формула> : 3 + 5 * <число> : 3 + 5 * 2 Сокращенно наличие вывода (цепочки преобразований) будем за- писывать в виде <формула>::3+5*2. Большинство грамматик допус- кают несколько различных выводов для одной и той же цепочки из языка. Постройте другой вывод для цепочки "3+5*2" - упражнение. Если в процессе вывода цепочки правила грамматики применяют- ся только к самому левому нетерминалу, говорят, что получен ле- вый вывод цепочки. Аналогично определяется правый вывод. Какой вывод построен в предыдущем примере? Изобразим выполняемые замены цепочек в виде т.н. "дерева разбора" (или дерева вывода). По традиции дерево изображается "вверх ногами": <формула> / \ \ <формула> \ <формула> / | \ \ | <формула> <знак> <формула> | | / | | | | | | | | | <число> <знак> <число> <знак> <число> | | | | | 3 + 5 * 2 Нарисованное дерево имеет ветви (линии) и узлы (помечены терми- налами и нетерминалами), из которых растут ветви. Конечные узлы (терминалы) называются листьями. Понятия "поддерево", "корень дерева", видимо, не нуждаются в определении. Одно и то же дерево разбора может описывать различные выводы (в дереве не фиксирован порядок применения правил). Однако, между левыми (или правыми) выводами и деревьями разбора для цепочек существует однозначное соответствие (упражнение). Если для одной и той же цепочки можно изобразить два разных дерева разбора (или, что то же самое, построить, два разных правых вывода), грамматика называется неоднозначной. Описанная грамматика неоднозначна (почему? - упражнение). Тот же самый язык можно описать однозначной грамматикой: <формула> : <терм> | <терм><знак><формула> <терм> : (<формула>) | <число> <знак> : + | * Как изменится дерево разбора? - упражнение. Дерево разбора оп- ределяет некоторую структуру цепочки языка. Так, мы видим, что подцепочка "3+5" является "формулой". Это противоречит нашим (интуитивным) понятиям о смысле исходной формулы: 3+5 в отличие от 5*2 не является подвыражением. Мы можем учесть приоритет операций, изменив грамматику: <формула> : <терм> | <формула> + <терм> <терм> : <элемент> | <терм> * <элемент> <элемент> : (<формула>) | <число> Как добавить в язык операции вычитания и деления? - упражнение. Кроме привычной формы записи арифметических формул (т.н. "инфиксной", т.е. со знаком операции между операндами), широко распространена "постфиксная" (или обратная польская) форма за- писи, в которой операция расположена после операндов. Примеры: 2+3 2 3 + 2*3+4 2 3 * 4 + 2*(3+4) 2 3 4 + * В обратной польской записи скобки не требуются, а значение фор- мулы очень легко вычислить при использовании стека чисел - уп- ражнение. Перевод с одного языка на другой называется трансляцией или компиляцией. Так как любую инфиксную формулу можно переписать в постфиксную запись с сохранением порядка следования чисел (до- казательство - упражнение), то для компиляции формулы из ин- фиксной в постфиксную запись следует выделить внешнюю операцию, записать в постфиксной форме оба операнда и записать операцию вслед за ними. Для перевода операнда в постфиксную запись можно рекурсивно обратиться к программе перевода формулы. Однако следование правилам грамматики при компиляции формулы за один ее просмотр слева направо приводит к следующему фрагменту: CompForm() { CompForm() ... выполнение которого, конечно же, никогда не завершится. Пробле- ма возникла из-за того, что цепочки в левой и правой частях правила начинаются с одного нетерминала (говорят, что граммати- ка леворекурсивна). Если устранить левую рекурсию: <формула> : <терм> | <терм><плюс минус><формула> <терм> : <элемент> | <элемент><умножить разделить><терм> <плюс минус> : + | - <умножить разделить> : * | / то описанная проблема исчезнет, рекурсивный компилятор можно будет написать, однако появятся новые трудности (какое дерево разбора будет соответствовать цепочке "5-3-2"? - упражнение). Фактически, преобразовав грамматику, мы изменили порядок свертки операций. Традиционно операции одного приоритета выпол- няются слева направо (говорят, что операции левоассоциативны), а только что написанная грамматика определяет операции как пра- воассоциативные. Наиболее просто решить эту проблему можно, добавив в мета- язык НФБН символы итерации {} "повторить 0 или более раз". С применением новых обозначений грамматика легко запишется без левой рекурсии: <формула> : <терм> { <плюс минус> <формула> } <терм> : <элемент> { <умножить разделить> <элемент> } Написанный по этой грамматике рекурсивный компилятор также бу- дет выглядеть просто: char *infix, *postfix; /* указатели на входную и выход- ную цепочки */ CompForm() { /* компилировать формулу */ register char sign; CompTerm(); while ( (sign = *infix) == '+' || sign == '-' ) { ++infix; CompTerm(); *postfix++ = sign; *postfix++ = ' '; } } CompTerm() { /* компилировать терм */ register char sign; CompEl(); while ( (sign = *infix) == '*' || sign == '/' ) { ++infix; CompEl(); *postfix++ = sign; *postfix++ = ' '; } } CompEl () { /* компилировать элемент */ if ( *infix == '(' ) { ++infix; CompForm(); if ( *infix++ != ')' ) error(); } else { if ( !isdigit(*infix) ) error(); while ( isdigit( *infix ) ) *postfix++ = *infix++; *postfix++ = ' '; } } Использованная нами при написании компилятора техника носит название рекурсивного спуска. Входную цепочку мы просматриваем слева направо, дерево вывода проходим сверху вниз (т.е. от на- чального нетерминала <формула>). Функция error в компиляторе служит для вывода сообщения о том, что предъявленная цепочка не входит в язык арифметических формул. Если компилятор при получении на вход цепочки не выдает сообщения об ошибке, говорят, что эта цепочка допущена. Приве- дите примеры не входящих в язык арифметических формул цепочек, допускаемых компилятором - упражнение. Если разбор цепочки-программы сопровождается не переводом ее в другое представление для дальнейшего выполнения, а немедлен- ным исполнением (в нашем случае - вычислением значения), гово- рят, что программа интерпретируется. Задачи и упражнения: 1. Какой язык описывает грамматикa, является ли данная грамма- тика однозначной? - S : e | 0 S 0 | 1 S 1 - S : 0 S 1 | 0 1 - S : S S '+' | S S '*' | 'a' - S : '+' S S | '-' S S | 'a' - S : S '(' S ')' S | e - S : 'a' S 'b' S | 'b' S 'a' S | e - S : 'a' | S '+' S | S S | S '*' | '(' S ')' 2. Описать язык однозначной грамматикой: - обратная польская(постфиксная) запись арифметической формулы - префиксная арифметической формулы - арифметическое выражение из целых констант, имен переменных, бинарных операций '+', '-', '*', '/' и унарной операции '-' - левоассоциативный список имен, разделенных запятыми - правоассоциативный список имен, разделенных запятыми - римские цифры 3. Доказать, что двоичные числа, описываемые грамматикой n : 1 1 | 1 0 0 1 | n 0 | n n делятся на 3. Порождает ли данная грамматика все двоичные числа кратные трем? 4. Показать, что грамматика stmt : IF expr THEN stmt | IF expr THEN stmt ELSE stmt | OTHER не является однозначной. Преобразовать ее в однозначную грамма- тику, описывающую язык, в котором ELSE соответствует ближайшему незакрытому THEN. 5. Какие ограничения следует наложить на грамматику, чтобы при- менение рекурсивного спуска было возможно? 6. Напишите грамматики для следующих языков: 2 n m n m n а) 0 б) a b a b в) xx, где x - любая цепочка из нулей и единиц. Упражнения по программированию: 1. Переделайте компилятор так, чтобы он не допускал ошибочных (т.е. не порождаемых грамматикой) цепочек. 2. Добавьте в компилятор операцию возведения в степень (право- ассоциативная операция "^" с наивысшим приоритетом). 3. Переделайте компилятор формул в интерпретатор. 4. Добавьте в компилятор операцию "унарный минус", чтобы вход- ные цепочки типа -(-a*b+c) стали бы допустимыми и правильно компилировались. Цепочки вида --a, a+-b, конечно же, не дол- жны допускаться! 5. Реализуйте компилятор римские цифры -> арабские цифры 6. Реализуйте компилятор арабские цифры -> римские цифры В префиксной форме записи формулы знак операции предшествует операндам, например, 1+2*3 записывается в виде + 1 * 2 3 . (Именна эта форма записи была предложена Я.Лукасевичем и полу- чила название "польской"). 7. Вычислите значение формулы, записанной в префиксной записи, просматривая ее один раз слева направо. 8. Реализуйте компилятор формул инфиксная запись -> префиксная. 9. Реализуйте компилятор формул из постфиксной (или префиксной) записи в инфиксную. В каком направлении удобно просматривать исходную постфиксную (или префиксную) запись? Из-за наличия скобок такой перевод не является однозначным, поэтому две задачи: - в инфиксной записи допускаются "лишние" скобки; - "лишние" скобки не допускаются.
Оставить комментарий
Комментарии
1.
6 декабря 2009, 19:40:22
спасибо, нашел реферат!
2.
+1 / -1
14 декабря 2008, 16:38:17
ты орешь чувак, при чем тут твой реферат на тему компиляция?!
мля, ты еще и из 2005 года Х)
мля, ты еще и из 2005 года Х)
3.
17 октября 2005, 19:32:26
Мне нужен реферат на тему:"Компиляция"