Xreferat.com » Рефераты по информатике и программированию » Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода

Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода

Кафедра ЭВА


Отчет по курсовой работе

по дисциплине

«Системное Программное Обеспечение»

на тему

«Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода.

Интерпретатор языка деревьев вывода»


Москва 2009

Аннотация


Цель данной курсовой работы:

– изучение принципов построения трансляторов

– написание на языке C++ класса, реализующего следующие действия над математическими выражениями:

– лексический анализ

– синтаксический анализ

– вычисление значения

– написание транслятора с языка математических выражений на язык деревьев вывода

– написание интерпретатора языка деревьев вывода



Теоретическое введение


Теория построения трансляторов используется во многих областях, связанных с программным обеспечением. Важность этой темы можно проиллюстрировать на примере языка высокого уровня C++: для разработки программы на C++ требуется гораздо меньше времени, чем на языках более низкого уровня.



Формальные грамматики


Формальное определение грамматики. Форма Бэкуса–Наура

Грамматика – это описание способа построения предложений некоторого языка. Иными словами, грамматика – это математическая система, определяющая язык. Фактически, определив грамматику языка, мы указываем правила порождения цепочек символов, принадлежащих этому языку. Таким образом, грамматика – это генератор цепочек языка.

Правило (или продукция) – это упорядоченная пара цепочек символов (α, β). В правилах важен порядок цепочек, поэтому их чаще записывают в виде α → β (или α::= β). Такая запись читается как «α порождает β» или «β по определению есть α».

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

Язык, заданный грамматикой G, обозначается как L(G).

Две грамматики G и G' называются эквивалентными, если они определяют один и тот же язык: L(G) = L(G'). Две грамматики G и G' называются почти эквивалентными, если заданные ими языки различаются не более чем на пустую цепочку символов: Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода

Формально грамматика G определяется как четверка G (VT, VN, P, S), где:

VT – множество терминальных символов или алфавит терминальных символов;

VN – множество нетерминальных символов или алфавит нетерминальных символов;

Р – множество правил (продукций) грамматики, вида Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, где Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода

S – целевой (начальный) символ грамматики Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода.

Алфавиты терминальных и нетерминальных символов грамматики не пересекаются: Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода. Целевой символ грамматики – это всегда нетерминальный символ. Множество Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода называют полным алфавитом грамматики G.

Множество терминальных символов VT содержит символы, которые входят в алфавит языка, порождаемого грамматикой. Как правило, символы из множества VT встречаются только в цепочках правых частей правил. Множество нетерминальных символов VN содержит символы, которые определяют слова, понятия, конструкции языка. Каждый символ этого множества может встречаться в цепочках как левой, так и правой частей правил грамматики, но он обязан хотя бы один раз быть в левой части хотя бы одного правила.

Во множестве правил грамматики может быть несколько правил, имеющих одинаковые левые части, вида: Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода. Тогда эти правила объединяют вместе и записывают в виде: Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода. Одной строке в такой записи соответствует сразу n правил.

Такую форму записи правил грамматики называют формой Бэкуса–Наура. Форма Бэкуса–Наура предусматривает, как правило, также, что нетерминальные символы берутся в угловые скобки: < >.

Пример грамматики, определяющей язык целых десятичных чисел со знаком:

С({0,1,2. 3,4.5. 6,7.8.9.-,+}, {<число>,<чс>.<цифра>}, Р,<число>)

P:

<<число> -> <чс> | +<чс> | -<чс>

<<чс> -> <цифра> | <чс><цифра>

<<цифра> ->0|1|2|3|4|5|6|7|8|9

Рассмотрим составляющие элементы грамматики G:

множество терминальных символов VT содержит двенадцать элементов: десять десятичных цифр и два знака;

множество нетерминальных символов VN содержит три элемента: символы <число>, <чс> и <цифра>;

множество правил содержит 15 правил, которые записаны в три строки (то есть имеется только три различных правых части правил);

целевым символом грамматики является символ <число>.

Названия нетерминальных символов не обязаны быть осмысленными. Это сделано для удобства понимания правил грамматики человеком. Например, в программе на языке Pascal можно изменить имена идентификаторов, и при этом не изменится смысл программы. Для терминальных символов это неверно. Набор терминальных символов всегда строго соответствует алфавиту языка, определяемого грамматикой.

Принцип рекурсии в правилах грамматики

Особенность рассмотренных выше формальных грамматик в том, что они позволяют определить бесконечное множество цепочек языка с помощью конечного набора правил. Приведенная выше в примере грамматика для целых десятичных чисел со знаком определяет бесконечное множество целых чисел с помощью 15 правил. В такой форме записи грамматики возможность пользоваться конечным набором правил достигается за счет рекурсивных правил. Рекурсия в правилах грамматики выражается в том, что один из нетерминальных символов определяется сам через себя. Рекурсия может быть непосредственной (явной) – тогда символ определяется сам через себя в одном правиле, либо косвенной (неявной) – тогда то же самое происходит через цепочку правил.

В рассмотренной выше грамматике G непосредственная рекурсия присутствует в правиле: <чс> Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода <чс><цифра>.

Чтобы рекурсия не была бесконечной, для участвующего в ней нетерминального символа грамматики должны существовать также и другие правила, которые определяют его, минуя его самого, и позволяют избежать бесконечного рекурсивного определения (в противном случае этот символ в грамматике был бы просто не нужен). Такими правилами являются <чс>Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода<цифра> – в грамматике G.

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

Если попытаться дать определение тому, что же является числом, то начать можно с того, что любая цифра сама по себе есть число. Далее можно заметить, что любые две цифры – это тоже число, затем – три цифры и т.д. Если строить определение числа таким методом, то оно никогда не будет закончено (в математике разрядность числа ничем не ограничена). Однако можно заметить, что каждый раз, порождая новое число, мы просто дописываем цифру справа (поскольку привыкли писать слева направо) к уже написанному ряду цифр. А этот ряд цифр, начиная от одной цифры, тоже в свою очередь является числом. Тогда определение для понятия «число» можно построить таким образом: «число – это любая цифра либо другое число, к которому справа дописана любая цифра». Именно это и составляет основу правил грамматики G и отражено в правилах

<чс> Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода <цифра> | <чс><цифра>. Другие правила в этой грамматике позволяют добавить к числу знак и дают определение понятию «цифра».

Принцип рекурсии – важное понятие в представлении о формальных грамматиках. Так или иначе, явно или неявно рекурсия всегда присутствует в грамматиках любых реальных языков программирования. Именно она позволяет строить бесконечное множество цепочек языка. Как правило, в грамматике реального языка программирования содержится не одно, а целое множество правил, построенных с помощью рекурсии.

Задача разбора

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

Грамматики и распознаватели – два независимых метода, которые реально могут быть использованы для определения какого-либо языка. Однако при создании компилятора для некоторого языка программирования возникает задача, которая требует связать между собой эти методы задания языков. Разработчиков компиляторов интересуют, прежде всего, синтаксические конструкции языков программирования. Для этих конструкций доказано, что задача разбора для них разрешима. Более того, для них найдены формальные методы ее решения. Поскольку языки программирования не являются чисто формальными языками и несут в себе некоторый смысл (семантику), то задача разбора для создания реальных компиляторов понимается несколько шире, чем она формулируется для чисто формальных языков. Компилятор должен не просто установить принадлежность входной цепочки символов заданному языку, но и определить ее смысловую нагрузку. Для этого необходимо выявить те правила грамматики, на основании которых цепочка была построена.

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

Виды рекурсий и разбора

Правила, имеющие вид

1) T-> T * A

2) T-> A * T

Называются леворекурсивными и праворекурсивными соответственно.

Разбор входной программы может быть как нисходящим так и восходящим. При нисходящем разборе недопустима левая рекурсия в правилах, т. к. она приводит к бесконечному зацикливанию. Левую рекурсию можно преобразовать в эквивалентую праворекурсивную форму следующим образом:

T-> A M

M-> * A | M

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

Классификация языков и грамматик

Чем сложнее язык программирования, тем выше вычислительные затраты компилятора на анализ цепочек исходной программы, написанной на этом языке, а следовательно, сложнее сам компилятор и его структура. Для некоторых типов языков в принципе невозможно построить компилятор, который бы анализировал исходные тексты на этих языках за приемлемое время на основе ограниченных вычислительных ресурсов (именно поэтому до сих пор невозможно создавать программы на естественных языках, например на русском или английском).

Классификация грамматик по Хомскому

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

Хомский выделил четыре типа грамматик.

Тип 0: грамматики с фразовой структурой

На структуру их правил не накладывается никаких ограничений: для грамматики вида G (VT, VN, P, S), Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода правила имеют вид: Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, где Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода.

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

Практического применения грамматики, относящиеся только к типу 0, не имеют.

Тип 1: контекстно-зависимые (КЗ) и неукорачивающие грамматики

В этот тип входят два основных класса грамматик:

Контекстно-зависимые грамматики G (VT, VN, P, S), Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев выводаимеют правила вида:

Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, где Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода

Неукорачивающие грамматики G (VT, VN, P, S), Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода имеют правила вида:

Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, где Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода

Структура правил КЗ-грамматик такова, что при построении предложений заданного ими языка один и тот же нетерминальный символ может быть заменен на ту или иную цепочку символов в зависимости от того контекста, в котором он встречается. Именно поэтому эти грамматики называют «контекстно-зависимыми». Цепочки Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода и Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода в правилах грамматики обозначают контекст (Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода – левый контекст, а Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода- правый контекст), в общем случае любая из них (или даже обе) может быть пустой. Неукорачивающие грамматики имеют такую структуру правил, что при построении предложений языка, заданного грамматикой, любая цепочка символов может быть заменена на цепочку символов не меньшей длины. Отсюда и название «неукорачивающие».

Доказано, что эти два класса грамматик эквивалентны.

При построении компиляторов такие грамматики не применяются, поскольку синтаксические конструкции языков программирования, рассматриваемые компиляторами, имеют более простую структуру и могут быть построены с помощью грамматик других типов. Что касается семантических ограничений языков программирования, то с точки зрения затрат вычислительных ресурсов их выгоднее проверять другими методами, а не с помощью контекстно-зависимых грамматик.

Тип 2: контекстно-свободные (КС) грамматики

Контекстно-свободные (КС) грамматики G (VT, VN, P, S), Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода имеют правила вида:Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, где Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода. Такие грамматики также иногда называют неукорачивающими контекстно-свободными (НКС) грамматиками (видно, что в правой части правила у них должен всегда стоять как минимум один символ). Существует также почти эквивалентный им класс грамматик – укорачивающие контекстно-свободные (УКС) грамматики G (VT, VN, P, S), Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, правила которых могут иметь вид: Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, гдеОднопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода.

Разница между этими двумя классами грамматик заключается лишь в том, что в УКС-грамматиках в правой части правил может присутствовать пустая цепочка (X), а в НКС-грамматиках – нет. Доказано, что эти два класса грамматик почти эквивалентны.

КС-грамматики широко используются при описании синтаксических конструкций языков программирования. Синтаксис большинства известных языков программирования основан именно на КС-грамматиках.

Внутри типа КС-грамматик кроме классов НКС и УКС выделяют еще целое множество различных классов грамматик, и все они относятся к типу 2.

Тип 3: регулярные грамматики

К типу регулярных относятся два эквивалентных класса грамматик: леволинейные и праволинейные.

Леволинейные грамматики G (VT, VN, P, S), Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев выводамогут иметь правила двух видов: Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев выводаили Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, где Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода.

В свою очередь, праволинейные грамматики G (VT, VN, P, S), Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода могут иметь правила тоже двух видов: Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев выводаили Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, гдеОднопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода.

Эти два класса грамматик эквивалентны и относятся к типу регулярных грамматик.

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

Соотношения между типами грамматик

Типы грамматик соотносятся между собой особым образом. Из определения типов 2 и 3 видно, что любая регулярная грамматика является КС-грамматикой, но не наоборот. Также очевидно, что любая грамматика может быть отнесена к типу 0, поскольку он не накладывает никаких ограничений на правила. В то же время существуют укорачивающие КС-грамматики (тип 2), которые не являются ни контекстно-зависимыми, ни неукорачивающими (тип 1), поскольку могут содержать правила вида Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода, недопустимые в типе 1.

Одна и та же грамматика в общем случае может быть отнесена к нескольким классификационным типам (например, как уже было сказано, все без исключения грамматики могут быть отнесены к типу 0). Для классификации грамматики всегда выбирают максимально возможный тип, к которому она может быть отнесена. Сложность грамматики обратно пропорциональна номеру типа, к которому относится грамматика.



Трансляторы


Формальное определение транслятора

Транслятор – это программа, которая переводит программу на исходном (входном) языке в эквивалентную ей программу на результирующем (выходном) языке.

С точки зрения преобразования предложений входного языка в эквивалентные им предложения выходного языка транслятор выступает как переводчик.

Результатом работы транслятора будет результирующая программа, но только в том случае, если текст исходной программы является правильным – не содержит ошибок с точки зрения синтаксиса и семантики входного языка. Если исходная программа неправильная (содержит хотя бы одну ошибку), то результатом работы транслятора будет сообщение об ошибке.

Этапы трансляции. Общая схема работы транслятора

На рис. 2.1 представлена общая схема работы компилятора. Из нее видно, что в целом процесс компиляции состоит из двух основных этапов – анализа и синтеза. На этапе анализа выполняется распознавание текста исходной программы, создание и заполнение таблиц идентификаторов. Результатом его работы служит некое внутреннее представление программы, понятное компилятору. На этапе синтеза на основании внутреннего представления программы и информации, содержащейся в таблице идентификаторов, порождается текст результирующей программы. Результатом этого этапа является объектный код. Кроме того, в составе компилятора присутствует часть, ответственная за анализ и исправление ошибок, которая при наличии ошибки в тексте исходной программы должна максимально полно информировать пользователя о типе ошибки и месте ее возникновения. В лучшем случае компилятор может предложить пользователю вариант исправления ошибки.

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


Однопроходный/двухпроходный транслятор с языка математических выражений на язык деревьев вывода


Компилятор в целом, с точки зрения теории формальных языков, выполняет две основные функции:

– распознавание языка исходной программы

– генерация результирующей программы на заданном языке.

Далее дается перечень основных фаз (частей) компиляции и краткое описание их функций.

Лексический анализатор (сканер) – это часть компилятора, которая читает литеры программы на исходном языке и строит из них слова (лексемы) исходного языка. На вход лексического анализатора поступает текст исходной программы, а выходная информация передается для дальнейшей обработки компилятором на этапе синтаксического разбора. С теоретической точки зрения лексический анализатор не является обязательной, необходимой частью компилятора. Однако существуют причины, которые определяют его присутствие практически во всех компиляторах.

Синтаксический анализатор – это основная часть компилятора на этапе анализа. Она выполняет выделение синтаксических конструкций в тексте исходной программы, обработанном лексическим анализатором. На этой же фазе компиляции проверяется синтаксическая правильность программы. Синтаксический разбор играет роль распознавателя текста входного языка программирования.

Семантический анализатор – это часть компилятора, проверяющая правильность текста исходной программы с точки зрения семантики входного языка. Кроме непосредственно проверки семантический анализ должен выполнять преобразования текста, требуемые семантикой входного языка (например, такие, как добавление функций неявного преобразования типов). В различных реализациях компиляторов семантический анализ может частично входить в фазу синтаксического разбора, частично – в фазу подготовки к генерации кода.

Подготовка к генерации кода – это фаза, на которой компилятором выполняются предварительные действия, непосредственно связанные с синтезом текста результирующей программы, но еще не ведущие к порождению текста на выходном языке. Обычно в эту фазу входят действия, связанные с идентификацией элементов языка, распределением памяти и т.п.

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

Проход транслятора – процесс последовательного чтения компилятором данных из памяти, их обработки и помещёния результата в память. В компиляторе может быть реализовано несколько проходов, например проходы лексического и синтаксического анализатора. В некоторых случаях проходы могут быть объединены в один проход.

Интерпретаторы

Интерпретатор – программа, воспринимающая исходную программу на входном (исходном) языке и выполняющая ее.

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



Lex и Yacc


Обзор генераторов кода

Системы GNU/Linux поставляются с несколькими программами для написания программ. Возможно наиболее популярны:

Flex, генератор лексического анализатора

Bison, генератор синтаксического анализатора

Gperf, развитый генератор хэш-функции

Эти программы генерируют тексты для языка C. Вы можете удивиться, почему они реализованы в виде генераторов кода,

Если Вам нужна помощь с академической работой (курсовая, контрольная, диплом, реферат и т.д.), обратитесь к нашим специалистам. Более 90000 специалистов готовы Вам помочь.
Бесплатные корректировки и доработки. Бесплатная оценка стоимости работы.

Поможем написать работу на аналогичную тему

Получить выполненную работу или консультацию специалиста по вашему учебному проекту

Похожие рефераты: