Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Теория автоматов и ФЯ.doc
Скачиваний:
29
Добавлен:
01.12.2018
Размер:
818.18 Кб
Скачать

1.5. Классификация языков по Хомскому

Если две различные грамматики порождают один и тот же язык, т.е. совпадающие множества терминальных цепочек, то они называются эквивалентными. Такие грамматики должны иметь одинаковые алфавиты символов языка Σ, но могут иметь разные алфавиты символов грамматики N и (или) множества порождающих правил P.

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

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

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

1.6. Задача распознавания цепочек языка

Пусть задана грамматика G(L) = {Σ, N, S, P} и цепочка γ терминальных символов алфавита Σ. Задача распознавания состоит в том, чтобы ответить на вопрос, принадлежит ли цепочка γ языку, порождаемому грамматикой G(L). Эту задачу можно решить следующим образом: попробовать построить последовательность применений правил грамматики, порождающих цепочку γ. Если удается найти такую последовательность, то ответом будет «да», в противном случае – «нет». Построенную при ответе «да» последовательность применений правил называют грамматическим разбором цепочки. Грамматический разбор необходим, если требуется произвести трансляцию цепочки, т.е. ее перевод в некоторую другую форму.

Сложность задачи распознавания зависит от сложности грамматики. Доказано, что для грамматики класса 0 в общем случае эта задача алгоритмически неразрешима, т.е. не существует универсального алгоритма, который для любой грамматики класса 0 и любой входной цепочки после конечного числа шагов выдаст ответ «да» или «нет». Алгоритмы распознавания построены для грамматик классов 1, 2 и 3, причем как для однозначных, так и для неоднозначных грамматик. Сложность алгоритмов распознавания тем меньше, чем проще грамматика. Самый простой и эффективный алгоритм существует для распознавания однозначных грамматик класса 3 (А-грамматик).

Порождающие грамматики применяются для решения задач распознавания самых различных языков. Для естественных языков (английского, русского и др.) их применение ограниченно: в настоящее время удалось построить грамматики (КЗ-грамматики) только для упрощенных подмножеств естественных языков. В то же время для большинства языков программирования (языков высокого уровня) однозначные КС-грамматики явились мощным инструментом для создания трансляторов. Что же касается машинных языков, а также автокодов, ассемблеров, командных языков, то для их описания оказалось достаточным применение однозначных А-грамматик.

2. Принципы трансляции языков программирования

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

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

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

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

В целом работа компилятора содержит следующие этапы:

1) лексический анализ, выделение лексем и формирование таблиц, содержащих информацию о лексемах (типы имен переменных, значения констант);

2) синтаксический анализ, перевод программы во внутреннюю форму;

3) оптимизация внутренней формы программы;

4) генерация машинных команд;

5) оптимизация машинных команд.

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

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

Кросс-компилятор реализуется как программа, выполняющаяся на некотором рабочем процессоре (и операционной системе), но генерирующая команды для другого (объектного) процессора. При этом, чтобы отладить такую программу, необходима программа-эмулятор, которая могла бы на рабочем процессоре моделировать выполнение команд объектного процессора. Программа-эмулятор необходима еще и потому, что на ней можно произвести детальную диагностику во время выполнения программы. Это особенно важно, если объектный процессор является встроенным, и в нем и его операционной системе не предусмотрены средства отладки. Заметим, что описать машинный язык, являющийся входным для программы-эмулятора, можно средствами А-грамматик.

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

1) лексического анализа и выделения лексем;

2) синтаксического анализа и генерации программы на промежуточном языке;

3) исполнения (интерпретации) программы на промежуточном языке.

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