Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
СПО юнита 2.doc
Скачиваний:
38
Добавлен:
17.11.2019
Размер:
5.82 Mб
Скачать

2.2 Разновидности языкового процессора

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

– точным;

– лаконичным, чтобы компилятор не был слишком большого размера;

– машинно-читаемым.

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

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

{xn | n > 0}

Здесь знак "|" можно прочесть как "где", nцелое.

Ниже проводится другой пример языка

n yn >0}

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

ху

хххууу

ххххххххуууууууу

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

х*y*

Здесь символ "*" (звездочка Клини) обозначает, что предшествующий ему элемент, употребляется нуль или большее число раз. Если в каждой строке языка должно находиться минимум по одному элементу х и у, то язык можно определить следующим образом:

хх*уу*

Возможна альтернативная запись

x+y+

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

x+ y+

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

(a b)*

представляет язык, строки которого состоят из нуля или большего числа элементов а или b. В последнем выражении скобки употребляются для того, чтобы показать более высокий приоритет знака | над знаком *. Принято, что знак * имеет более высокий приоритет, чем , поэтому язык будет содержать только строки, состоящие из одного элемента а или же нуля или большего числа элементов b. Приведем другой пример регулярного выражения

(aab | ab)*

Этот язык будет включать в себя следующие строки:

aababaab

ababab

aabaabaabab

Регулярное выражение

(aaa | ab)*

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

Введем понятие алфавита. Алфавит – конечный набор символов, подобный приведенным ниже:

{0,1}

{…}

{0 – 9}

Если А – алфавит, то к числу регулярных выражений относятся:

– нулевая строка, обозначаемая ;

– любой элемента А.

Кроме того, если P и Q – регулярные выражения, то регулярными являются также следующие выражения:

– PQ (Q следует после Р);

– P\Q (Р или Q);

– Р* (нуль или более вхождений Р).

Введенный ранее оператор + (одно или более вхождений элемента) не является оператором регулярного выражения, поскольку при описании любого регулярного выражения можно обойтись и без него. Это связано с тем, что любое выражение, содержащее +, можно заменить эквивалентным выражением, содержащим оператор конкатенации и оператор. Например, выражение (abc)+ эквивалентно (abc)(abc)*.

Поскольку включение оператора + не расширяет возможности формы записи, его часто используют так, как будто он является оператором регулярного выражения.

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

( d)*

Здесь  обозначает букву, a d – цифру.

Один из языков

(xnyn  n > 0)

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

S xSy

S xy

Здесь символ "" означает "может иметь вид". Продукции могут использоваться для генерации строк языка с использованием следующих правил.

1. Начать с символа S и заменить его строкой, расположенной справа от знака продукции.

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

Примеры последовательности строк:

S

xSy

xxSyy

хххууу

Обычно подобную последовательность записывают следующим образом:

S => xSy => xxSyy => хххууу.

Здесь знак "=>" читается как "порождает". Последовательность шагов, использованная для генерации строки с применением продукций грамматики, называется порождением строки.

Очевидно, что описанным выше способом могут быть получены все строки языка

(xnyn n > 0).

При этом не будет порождена ни одна строка, не принадлежащая указанному языку.