
- •Теоретические основы формальных языков и их трансляции (магистратура)
- •1. Грамматики и языки.
- •1.1. Обсуждение грамматик.
- •1.2. Символы и цепочки.
- •1.3. Формальное определение грамматики и языка.
- •1.4. Синтаксические деревья и неоднозначность.
- •1.5. Задача разбора.
- •1.6. Некоторые отношения применительно к грамматикам.
- •1.7. Практические ограничения, налагаемые на грамматики.
- •1.9. Некоторые проблемы теории формальных языков.
- •2. Регулярные выражения и конечные автоматы.
- •2.1. Диаграммы состояний.
- •2.2. Детерминированный конечный автомат.
- •2.4. Построение ка из нка.
- •3. Нисходящие распознаватели.
- •3.1. Нисходящий разбор с возвратами.
- •3.2. Проблемы нисходящего разбора и их решение.
- •5.3. Лексический анализ.
- •5.4. Синтаксический анализ.
- •5.5. Генерация кода.
- •5.6. Оптимизация кода.
- •5.7. Анализ и исправление ошибок.
- •5.8. Принципиальная модель компилятора.
- •1.1. Обсуждение грамматик
5.3. Лексический анализ.
Первая фаза — лексический анализ. Входом компилятора, а следовательно, и лексического анализатора, служит цепочка символов некоторого алфавита. Алфавит Pascal
А В С ... Z
0 ... 9 _ пробел
+ - / * = < > [ ] . , ; : ( ) ’
Основные символы языка (терминалы) подразделяются на буквы цифры и специальные символы – знаки, имеющие фиксированный смысл.
Зарезервированное cлово = and array begin case const div downto do else end file for function goto if in label mod nil not of or packed procedure program record repeat set then to type until var while with.
В программе некоторые комбинации символов часто рассматриваются как единые объекты. Среди типичных примеров можно указать следующие:
1) В таких языках, как Pascal, цепочка, состоящая из одного или более пробелов, обычно рассматривается как один пробел.
2) В языках есть ключевые слова, такие, как Begin, End, integer, и составные знаки :=, <=,>= и т.д. каждое из которых считается одним символом (зарезервированные слова).
3) Каждая цепочка, представляющая числовую константу, рассматривается как один элемент текста.
4) Идентификаторы, используемые как имена переменных, функций, процедур, меток и т. п., также считаются лексическими единицами языка программирования.
Работа лексического анализатора состоит в том, чтобы сгруппировать определенные терминальные символы в единые синтаксические объекты, называемые лексемами. Какие объекты считать лексемами, зависит от определения языка программирования. Лексема - это цепочка терминальных символов, с которой мы связываем лексическую структуру, состоящую из пары вида (тип лексемы, некоторые данные). Первой компонентой пары является синтаксическая категория, такая как «константа» или «идентификатор», а вторая - указатель: в ней указывается адрес ячейки, хранящей информацию об этой конкретной лексеме. Для данного языка число типов лексем предполагается конечным. Пару (тип лексемы, указатель) тоже будем называть лексемой, когда это не будет вызывать недоразумений.
Таким образом, лексический анализатор - это транслятор, входом которого служит цепочка символов, представляющая исходную программу, а выходом - последовательность лексем. Этот выход образует вход синтаксического анализатора.
П р и м е р 5.1. Рассмотрим следующий оператор присваивания из языка, подобного Pascal:
COST := (PRICE + TAX) * 30
На этапе лексического анализа будет обнаружено, что COST, PRICE и TAX - лексемы типа идентификатора, а 30 - лексема типа константы. Знаки := ( + ) * сами являются лексемами. Допустим, что все константы и идентификаторы нужно отобразить в лексемы типа <ид>. Мы предполагаем, что вторая компонента лексемы представляет собой указатель элемента таблицы, содержащего фактическое имя идентификатора вместе с другими, собранными нами данными об этом конкретном идентификаторе. Первая компонента используется синтаксическим анализатором для разбора. Вторая компонента используется на этапе генерации кода для изготовления подходящего машинного кода.
Таким образом, выходом лексического анализатора, работающего на нашей входной цепочке, будет последовательность лексем
<ид>1 := (<ид>2 + <ид>3) * <ид>4
Здесь вторая компонента лексемы (указатель данных) показана в виде нижнего индекса. Символы :=, +, * трактуются как лексемы, тип которых представлен ими самими. Они не имеют связанных с ними данных и, значит, не имеют указателей.
Лексический анализ провести легко, если лексемы, состоящие более чем из одного знака, изолированы с помощью знаков, которые сами являются лексемами. В примере знаки :=, +,* не могут быть частью идентификатора, так что COST, PRICE и TAX легко выделяются как лексемы.
Мы определим два крайних подхода к лексическому анализу. Большинство известных способов основано на том или другом из этих подходов, а некоторые на их комбинации.
(1) Говорят, что лексический анализатор работает прямо, если для данного входного текста (цепочки) и положения указателя в этом тексте анализатор определяет лексему, расположенную непосредственно справа от указываемого места, и сдвигает указатель вправо от части текста, образующей эту лексему.
(2) Говорят, что лексический анализатор работает не прямо, если для данного текста, положения указателя в этом тексте и типа лексемы он определяет, образуют ли знаки, расположенные непосредственно справа от указателя, лексему этого типа. Если да, то указатель передвигается вправо от части текста, образующей эту лексему.
Вообще мы будем описывать алгоритмы синтаксического анализа в предположении, что лексический анализ прямой. В случае непрямого лексического анализа можно использовать “недетерминированные” алгоритмы или алгоритмы с возвратами.
После того как в результате лексического анализа лексемы распознаны, информация о некоторых из них собирается и запасается в одной или нескольких таблицах. Какова эта информация, зависит от языка. В случае Pascal, например, мы хотели бы знать, что COST, PRICE и TAX – переменные с плавающей точкой, а 30 – целая константа .
В сложных языках, таких, как Pascal, объем сведений, которые надо запомнить о данной переменной, может быть очень велик.
Рассмотрим несколько упрощенный пример таблицы, в которой хранится информация об идентификаторах. Такую таблицу часто называют таблицей имен (а также таблицей идентификаторов и таблицей символов). В ней перечислены, в частности, все идентификаторы вместе с относящейся к ним информацией.
Допустим, что в тексте встречается оператор
COST := (PRICE + TAX) * 30
После просмотра этого оператора таблица может иметь вид таблицы 5.2. Если позднее во входной цепочке попадется идентификатор, надо справиться в этой таблице, не появлялся ли он раньше. Если да, то лексема, соответствующая новому вхождению этого идентификатора, будет той же, что и у предыдущего вхождения. Например, если в программе, написанной на Pascal, после указанного выше оператора следует оператор, содержащий переменную COST, то лексемой для второго вхождения COST должна быть <ид>1 - та же, что и для первого вхождения.
Таблица 5.2
Номер |
Идентификатор |
Информация
|
1 |
COST |
Переменная с плавающей точкой |
2 |
PRICE |
Переменная с плавающей точкой |
3 |
TAX |
Переменная с плавающей точкой |
4 |
30 |
Целая константа |
Таким образом, эта таблица должна обеспечивать
(1) быстрое добавление новых идентификаторов и новых сведений о них,
(2) быстрый поиск информации, относящейся к данному идентификатору.
Обычно применяют метод хранения данных с помощью таблиц расстановки.