Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
compilers.docx
Скачиваний:
9
Добавлен:
09.11.2018
Размер:
108.47 Кб
Скачать

2.2. Определение синтаксиса

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

Грамматика естественным образом описывает иерархическую структуру множества конструкций языка программирования. Например, инструкция if-else в С имеет вид

if (выражение) инструкция else инструкция

Таким образом, инструкция if-else представляет собой ключевое слово if, за которым следует открывающая круглая скобка, выражение, закрывающая скобка, инструкция, ключевое слово else и еще одна инструкция (в С нет ключевого слова then). Используя переменную ехрг для обозначения выражения и переменную stmt для обозначения инст­рукции, можно записать это структурное правило так:

stmt → if (ехрг) stmt else stmt (2.1)

Здесь символ (→) можно прочесть как "может иметь вид". Такое правило называется продукцией (production). В продукции лексические элементы вроде ключевого слова if и скобок называются токенами. Переменные типа ехрг и stmt представляют последова­тельности токенов6 и называются нетерминальными символами, или просто нетермина­лами (nonterminals).

Контекстно-свободная грамматика имеет четыре компонента.

1. Множество токенов, представляющих собой терминальные символы (или просто терминалы).

2. Множество нетерминальных символов.

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

4. Указание одного из нетерминальных символов как стартового, или начального.

Следует придерживаться правила, согласно которому грамматика определяется пере­числением ее продукций, причем первая продукция указывает стартовый символ. Цифры, знаки вроде <= и выделенные полужирным шрифтом слова типа while являются тер­минальными символами. Выделенные курсивом слова являются нетерминалами, а все слова или символы, поданные без выделения, могут рассматриваться как токены7. Для удобства записи правые части продукций с одними и теми же нетерминалами слева мо­гут быть сгруппированы с помощью символа "|" ("или").

Пример 2.1

В примерах этой главы используются выражения, состоящие из цифр и знаков "плюс" и "минус", например 9-5+2, 3-1 или 7. Поскольку знаки "плюс" и "минус" должны располагаться между двумя цифрами, такие выражения можно рассматривать как списки цифр, разделенных знаками "плюс" и "минус". Синтаксис используемых вы­ражений описывает грамматика из следующих продукций:

list → list + digit (2.2)

list → list - digit (2.3)

list → digit (2.4)

digit0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 (2.5)

Правые части трех продукций с нетерминалом list в левой части могут быть объединены:

list —> list + digit | list - digit | digit

Здесь, в соответствии с нашими соглашениями, токенами грамматики являются символы

+ - 0123456789

Нетерминальными символами являются выделенные курсивом имена list и digit, при этом нетерминал list — стартовый; именно его продукция дана первой.

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

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

Пример 2.2

Язык, определяемый грамматикой из примера 2.1, состоит из списков цифр, разде­ленных знаками "плюс" и "минус".

Десять продукций для нетерминала digit позволяют ему быть любым из токенов О, 1, 9. Из продукции (2.4) следует, что список может состоять и из одной цифры, т.е. цифра сама по себе является списком. Продукции (2.2) и (2.3) выражают тот факт, что если мы возьмем любой список и добавим к нему знак "плюс" или "минус" с после­дующей цифрой, то получим новый список.

Оказывается, что продукции (2.2) - (2.5) — все, что надо для определения языка. На­пример, можно сделать вывод, что 9-5+2 является списком, следующим образом.

1. 9 — список в соответствии с продукцией (2.4), поскольку 9 — цифра.

2. 9-5 — список в соответствии с продукцией (2.3), так как 9 — список, а 5 — цифра.

3. 9-5+2— список в соответствии с продукцией (2.2), поскольку 9-5— список, а 2 — цифра.

Данное утверждение продемонстрируем деревом, представленным на рис. 2.2. Каж­дый узел дерева помечен символом грамматики. Внутренний узел и его дочерние узлы соответствуют продукции, причем узел соответствует левой части продукции, а потом­ки — правой. Такие деревья называются деревьями разбора (parse trees); они будут рас­смотрены ниже. □

Рис. 2.2. Дерево разбора выражения 9-5+2 в соответствии с грамматикой из примера 2.1

Пример 2.3

Еще один пример списков — последовательность инструкций, разделенных точками с запятыми; такую последовательность можно найти в Pascal, в блоке begin-end. Однако между токенами begin и end может и не быть инструкций. Разработку грамматики для блока begin-end можно начать со следующих продукций:

block → begin opt stmts end

opt_stmts → stmt_list | ε

stmt_list → stmt_list; stmt | stmt

Заметьте, что правой частью для opt_stmts может являться е, т.е. пустая строка сим­волов. Таким образом, opt_stmts может быть заменено пустой строкой, и блок может ока­заться строкой из двух токенов begin end. Обратите также внимание, что продукции для stm_list аналогичны продукциям для list в примере 2.1 (точка с запятой вместо арифме­тической операции и с stmt вместо digit). В данном примере не представлены продукции для stmt, но далее вкратце будут рассмотрены продукции для различных инструкций, та­ких как инструкции присвоения, инструкции if и др. □

Деревья разбора

Дерево разбора наглядно показывает, как стартовый символ грамматики порождает строку языка. Если нетерминал А имеет продукцию AXYZ, то дерево разбора может иметь внутренний узел А с тремя потомками, помеченными слева направо как X, Y и Z.

Формально для данной контекстно-свободной грамматики дерево разбора представ­ляет собой дерево со следующими свойствами.

1. Корень дерева помечен стартовым символом.

2. Каждый лист помечен токеном или е.

3. Каждый внутренний узел представляет нетерминальный символ.

4. Если А является нетерминалом и помечает некоторый внутренний узел, а Х1 Х2,

Хn — отметки его дочерних узлов, перечисленные слева направо, то А → Х1 Хг ... Хnпродукция. Здесь X1, Х2, … , Хn могут представлять собой как терминальные, так и нетерминальные символы. В качестве специального случая продукции А ε соот­ветствует узел А с единственным дочерним узлом ε.

Пример 2.4

На рис. 2.2 корневой узел помечен нетерминалом list, стартовым символом грамма­тики из примера 2.1. Дочерние узлы имеют отметки list, + и digit. Заметьте, что

listlist + digit

является продукцией грамматики из примера 2.1. К левому дочернему узлу применен тот же шаблон, со знаком "минус" вместо знака "плюс". Все три узла, помеченные как digit, имеют по одному дочернему узлу с метками-цифрами. □

Листья дерева разбора, читаемые слева направо, образуют крону (yield) дерева, кото­рая представляет собой строку, выведенную, или порожденную из нетерминального сим­вола в корне дерева. На рис. 2.2 порожденная строка — 9–5+2. Здесь все листья показа­ны на одном, нижнем, уровне; в дальнейшем выравнивать листья деревьев таким обра­зом не будем. Они должны рассматриваться в определенном порядке слева направо: если а и b — два дочерних узла одного родителя и узел а находится слева от b, то все потомки а будут находиться слева от любого потомка b.

Другое определение языка, порожденного грамматикой, — это множество строк, ко­торые могут быть сгенерированы некоторым деревом разбора. Процесс поиска дерева разбора для данной строки токенов называется разбором, или синтаксическим анализом этой строки.

Неоднозначность

Будьте предельно внимательны при рассмотрении структуры строки, соответствую­щей грамматике. Очевидно, что каждое дерево порождает единственную строку (путем считывания листьев этого дерева), однако для данной строки токенов грамматика может иметь более одного дерева. Такая грамматика называется неоднозначной. Чтобы убе­диться в ее неоднозначности, достаточно найти строку токенов, которая имеет более од­ного дерева разбора. Поскольку такая строка обычно имеет не единственный смысл, сле­дует использовать либо однозначные (непротиворечивые), либо неоднозначные грамма­тики с дополнительными правилами для разрешения неоднозначностей.

Пример 2.5

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

string → string + string | string - string |0|1|2|3|4|5|6|7|8|9

Объединение записей для digit и list в один нетерминальный символ string имеет ка­жущийся смысл, поскольку отдельная цифра является специальным случаем списка.

Однако из рис. 2.3 видно, что выражение типа 9–5+2 теперь имеет больше одного дерева разбора. В данном случае два дерева разбора соответствуют двум вариантам рас­становки скобок в выражении: (9-5)+2 и 9-(5+2). Это второе выражение дает в ре­зультате значение 2 вместо обычного 6. Грамматика в примере 2.1 не допускает такой интерпретации. □

Рис. 2.3. Два дерева разбора для выражения 9-5+2

Ассоциативность операторов

По соглашению 9+5+2 эквивалентно (9+5)+2, а 9-5-2 эквивалентно (9-5)-2. Когда операнд типа 5 имеет операторы и слева, и справа, необходимо установить, какой именно оператор использует этот операнд. Мы говорим, что оператор + левоассоциативен, поскольку операнд со знаками "плюс" с обеих сторон используется левым операто­ром. В большинстве языков программирования четыре арифметических оператора — сложение, вычитание, умножение и деление — левоассоциативны.

Некоторые распространенные операторы, например возведение в степень, правоассоциативны. Другим примером правоассоциативного оператора может служить оператор присвоения (=) в С, где выражение а=b=с трактуется как а= (b=с).

Строки типа а=b=с с правоассоциативным оператором генерируются следующей грамматикой.

right → letter = right | letter

lettera|b|…|z

Различия между деревьями разбора для левоассоциативных операторов типа "-" и правоассоциативных операторов вроде "=" показаны на рис. 2.4. Обратите внимание, что дерево разбора для 9-5-2 растет вниз влево, в то время как дерево разбора для а=b=с вниз вправо.

Рис. 2.4. Деревья разбора для лево- и правоассоциативных операторов

1 В настоящее время наличие языка структурированных запросов SQL снимает вопрос о том, явля­ется ли интерпретация запросов задачей, всего лишь схожей с компиляцией. — Прим. перев.

2 Здесь следует обратить внимание на перевод термина statement. Дословно statement означает вы­сказывание, утверждение, однако в применении к компьютерной тематике это не совсем удачно. Обычно при переводе используется термин оператор, но в данной книге, посвященной формальным языкам, этот термин имеет собственное значение. Поэтому при переводе термина statement за редкими исключениями было использовано понятие инструкция — как наиболее близкое по смыслу, так и уже используемое при описании языка, например, в книге Б. Страуструп. Язык программирования С++, 3-е изд. — СПб.; М.: "НевскийДиалект"— "ИздательствоБИНОМ", 1999. Прим. перев.

3 Следует упомянуть о важности вопроса выделения памяти для размещения идентификаторов исход­ной программы. Как мы увидим в главе 7, "Среды времени исполнения", распределение памяти в процессе работы зависит от компилируемого языка. Принятие решения о выделении памяти происходит либо в про­цессе создания промежуточного кода, либо при генерации целевого кода.

4 "Почти произвольные" строки, поскольку производится только простое сканирование макроса. Как только при сканировании находится символ, соответствующий тексту, следующему после #i в шабло­не, сканированная строка считается соответствующей формальному параметру #i. Таким образом, если мы попытаемся подставить ab; cd вместо #1, обнаружим, что параметру #1 соответствует строка ab, а параметру #2 — строка cd.

5 Slanted — наклоненный. — Прим. перев.

6 Вообше говоря, нетерминал представляет множество последовательностей токенов. — Прим. ред.

7 Отдельные символы, выделенные курсивом, будут использоваться и для других целей при деталь­ном изучении грамматики в главе 4, "Синтаксический анализ". Например, они будут применяться при указании символов, которые могут представлять собой либо токены, либо нетерминалы. Однако выде­ленное курсивом имя из двух или более символов будет всегда означать нетерминал.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]