Московский государственный индустриальный университет
Опубликован: 27.09.2006 | Доступ: свободный | Студентов: 3337 / 383 | Оценка: 4.17 / 3.79 | Длительность: 24:17:00
Специальности: Программист
Лекция 12:

Проект "Компилятор формул"

Аннотация: Стековый калькулятор. Грамматики языка правильных арифметических формул. Рекурсивный компилятор формул. Стековый компилятор формул. Интерпретатор арифметических выражений. Тексты эталонных проектов.
Ключевые слова: рекурсивный компилятор формул, стековый компилятор формул, утилита, информация, стековый калькулятор, объект, контейнер, стек, класс, интерфейс, семантика, арифметическая операция, операции, аргумент, вызов метода, запись, программа, ПО, компилятор, преобразования формул, множества, транслятор, алфавит, метаалфавит, метасимволы, грамматика, метасимвол, стартовый метасимвол, выводимая цепочка, терминал, нетерминал, язык порожденный граматикой, нормальной формой Бэкуса-Наура, правильная программа, умножение, имя переменной, вывод, дерево вывода, выражение, фигурные скобки, элемент языка, разрешимость, Java, входной, переменная, рекурсивнй компилятор, методы класса, xterm, компиляция, массив, длина, множитель, значение, завершение работы, конечные, запуск, команда, ассоциативность, функция, отрицание, алгоритм, доказательство, отложенная операция, precedence, анализ, интерпретатор, файл, система счисления, печать, целое число, конструктор класса, остаток от деления, битовые операции, деление, унарные операции, duplicate, диагностика, остаток, значение формулы, аплет, график, идентификатор

В данном параграфе изучаются простейшие вопросы теории компиляции, рассматриваются языки простейших арифметических формул и стекового калькулятора, а также задача автоматического перевода с первого на второй.

Первым вариантом компилятора — средства для автоматического перевода программ, является рекурсивный компилятор формул — достаточно простой, но обладающий рядом недостатков.

Другой, значительно более общий подход к решению этой задачи, осуществляется во втором программном проекте — стековом компиляторе формул. Этот проект, кроме реализации компилятора, предусматривает также и реализацию интерпретатора арифметических формул. Исходные тексты упомянутых программ в значительной мере оптимизированы, а для облегчения работы над проектами используется утилита make.

Дополнительная информация о различных подходах к реализации компиляторов может быть найдена в книге [9].

Стековый калькулятор

Стековый калькулятор, как это следует из его названия, представляет из себя некоторый объект, использующий хорошо известный нам контейнерстек. С формальной точки зрения стековый калькулятор — это класс, реализующий следующий интерфейс.

Интерфейс стекового калькулятора

interface StackCalc {
    // Добавить число в стек.
    void    push(int val);
    // Сложить.
    int     add() throws Exception;
    // Вычесть.
    int     sub() throws Exception;
    // Умножить.
    int     mul() throws Exception;
    // Разделить.
    int     div() throws Exception;
    // Показать вершину стека.
    int     top() throws Exception;
}

Методы push и top хорошо известны и комментариев не требуют, а семантика остальных четырех такова: из стека извлекаются (с помощью метода pop ) два верхних числа, над ними выполняется указанная в названии метода арифметическая операция, а результат кладется обратно в стек (с помощью метода push ). При этом в качестве первого аргумента арифметической операции берется тот из двух извлеченных элементов, который был положен в стек раньше другого. Если в момент вызова одного из этих четырех методов глубина стека меньше двух, возникает исключительная ситуация. При реализации данного интерфейса на базе ограниченного вектора выполнение метода push также может привести к исключительной ситуации, связанной с переполнением стека.

Стековый калькулятор можно использовать для вычисления значений различных арифметических выражений типа 5(7+8)+25. Нужно только написать предварительно программу для вычисления значения.

С целью сокращения длины подобной программы будем записывать последовательность вызываемых методов в строку, разделяя их просто пробелом. Названия методов арифметических операций заменим на соответствующие им знаки действий ( +, -, * и / ), вместо вызова метода push с аргументом val будем записывать только его аргумент, а завершающий любую программу вызов метода top вообще включать в такую сокращенную запись программы не будем.

Выполнение программы 5 7 8 + * 25 +

Рис. 12.1. Выполнение программы 5 7 8 + * 25 +

С использованием этих сокращений программа для вычисления выражения 5(7+8)+25 примет вид 5 7 8 + * 25 +. Рисунок 12.1 показывает последовательность состояний стека стекового калькулятора при выполнении этой программы.

В рассмотренном примере программа для стекового калькулятора была написана по исходной формуле 5(7+8)+25 нами. Основной задачей этого параграфа является реализация компилятора — программы, которая сможет осуществлять преобразование формулы в программу для калькулятора автоматически.

С формальной точки зрения компилятор представляет собой программную реализацию некоторой функции \tau, действующей из множества цепочек одного языка L_1 (в рассматриваемом случае это язык арифметических формул) в множество цепочек другого L_2 (язык программ стекового калькулятора) таким образом, что \forall \omega \in L_1 семантика цепочек \omega и \tau(\omega) \in L_2 совпадает. Говоря другими словами, компилятор (часто называемый также транслятором ) реализует перевод с одного языка на другой с сохранением смысла.

Напомним некоторые важнейшие определения, связанные с языками и грамматиками, которые были рассмотрены в "Высказывания и предикаты" .

Пусть \Sigma — некоторый алфавит, Nметаалфавит, т.е. какой-то другой алфавит, не пересекающийся с \Sigma ( \Sigma \cap N =
\varnothing ). Элементы метаалфавита N называются метасимволами. Грамматикой G называется набор ( \Sigma, N, P, S ), где \Sigma — множество символов, N — множество метасимволов, P — множество правил вывода вида: \alpha\rightarrow\beta, где \alpha\in N — какой-то метасимвол, \beta \in (\Sigma \cup N)^* — произвольная цепочка над объединением двух алфавитов, и для каждого \alpha\in N встречается хотя бы одно правило с \alpha в левой части (до стрелочки), а S \in N — так называемый стартовый метасимвол.

Содержательно каждое правило грамматики имеет смысл подстановки. Например, строка \alpha\rightarrow\alpha\gamma\alpha означает возможность замены метасимвола \alpha на цепочку \alpha\gamma\alpha. Начав со стартового символа и пользуясь различными правилами грамматики, мы можем получать различные цепочки из символов, которые называются выводимыми цепочками.

Заметим, что если в цепочке встречается метасимвол, то ее можно преобразовать дальше, применив одно из правил грамматики с этим метасимволом в левой части. Если же метасимволов в цепочке не осталось, то процесс ее преобразования закончен и больше с цепочкой ничего сделать нельзя. По этой причине обычные символы (из алфавита \Sigma ) часто называют терминалами, а метасимволы (из N ) — нетерминалами.

Языком L(G), порожденным грамматикой G, называется множество всех терминальных выводимых цепочек.

Для задания грамматики часто используют очень наглядную форму представления, называемую нормальной формой Бэкуса-Наура (НФБН). Набор правил P задают при этом в виде совокупности правил со стрелочками, перечисляющими все возможные цепочки, на которые может быть заменен каждый из метасимволов грамматики в процессе вывода, а стартовым метасимволом считается тот, который присутствует в левой части самого первого правила.

Возьмем в качестве алфавита \Sigma множество, состоящее из четырех знаков арифметических операций ( +, -, * и / ) и 26-и идентификаторов от a до z, которыми будут обозначаться произвольные целые числа: \Sigma = \{ +, -, *, /, a, b, \ldots, z \}.

Тогда язык \Sigma^* будет представлять из себя все возможные программы для стекового калькулятора, включая и неправильные. Например, пустая цепочка \varepsilon означает вызов единственного метода pop, что приведет к возникновению исключительной ситуации. Принадлежащая этому языку цепочка 2 + также соответствует некорректной программе, ибо в момент вызова метода add в стеке будет содержаться только один элемент.

Для описания языка правильных программ для стекового калькулятора можно воспользоваться следующей грамматикой G_S.

\begin{tabular}{lrl}
\ \ \ \ \ & $e$ $\rightarrow$ & $e\ e\ -$ \\
          & $\mid$& $e\ e\ +$ \\ 
          & $\mid$& $e\ e\ *$ \\ 
          & $\mid$& $e\ e\ /$ \\ 
          & $\mid$& $a$ $\mid$ $b$ $\mid$ $\ldots$ $\mid$ $z$ \\
\end{tabular}

Именно язык L(G_S) мы и будем рассматривать, как язык правильных программ для стекового калькулятора.

Анастасия Халудорова
Анастасия Халудорова
екатерина яковлева
екатерина яковлева