- •Введение
- •1. Основные понятия системного программного обеспечения
- •1.1. Понятия прикладного и системного программного обеспечения
- •1.2. Состав системного программного обеспечения
- •2. Состав и архитектура операционных систем
- •2.1. Состав операционных систем
- •2.2. Архитектура ос
- •3. Процессы и потоки
- •3.1. Концепция процессов и потоков
- •3.2. Многозадачность. Формы программной работы
- •3.3. Подсистема управления процессами и потоками
- •3.4. Роль процессов, потоков и волокон в многозадачности
- •3.5. Создание процессов
- •3.6. Потоки и их модели
- •3.7. Планирование и синхронизация процессов и потоков
- •3.7.1. Виды планирования
- •3.7.2. Алгоритмы планирования потоков
- •3.7.3. Алгоритмы приоритетного планирования
- •3.7.4. Взаимоисключения
- •3.7.5. Семафоры
- •3.7.6. Тупики
- •4. Управление памятью
- •4.1. Функции ос по управлению памятью
- •4.2. Классификация методов распределения памяти
- •4.3. Распределение памяти без использования внешней памяти
- •4.4. Методы структуризации виртуальной памяти
- •4.4.1. Страничная организация виртуальной памяти
- •4.4.2. Сегментная организация виртуальной памяти
- •4.4.3. Странично-сегментная организация памяти
- •5. Файловые системы
- •5.1. Цели и задачи файловой системы
- •5.2. Организация файлов и доступ к ним
- •5.3. Логическая организация файла
- •5.4. Каталоговые системы
- •5.5. Основные возможности файловой системы ntfs
- •5.6. Структура тома с файловой системой ntfs
- •5.7. Возможности ntfs по ограничению доступа к файлам и каталогам
- •6. Управление вводом-выводом
- •6.1. Физическая организация устройств ввода-вывода
- •6.2. Организация программного обеспечения ввода-вывода
- •6.3. Обработка прерываний
- •6.4. Драйверы устройств
- •6.5. Независимый от устройств слой ос
- •6.6. Пользовательский слой программного обеспечения
- •7. Построение операционных систем
- •7.1. Принципы построения операционных систем
- •7.1.1. Принцип модульности
- •7.1.2. Принцип функциональной избирательности
- •7.1.3. Принцип генерируемости ос
- •7.1.4. Принцип функциональной избыточности
- •7.1.5. Принцип виртуализации
- •7.1.6. Принцип независимости программ от внешних устройств
- •7.1.7. Принцип совместимости
- •7.1.8. Принцип открытой и наращиваемой ос
- •7.1.9. Принцип мобильности
- •7.1.10. Принцип обеспечения безопасности вычислений
- •7.2. Построение интерфейсов операционных систем
- •7.3. Интерфейс прикладного программирования
- •7.3.1. Реализация функций api на уровне ос
- •7.3.2. Реализация функций api на уровне системы программирования
- •7.3.3. Реализация функций api с помощью внешних библиотек
- •7.4. Классификация системных вызовов
- •7.5. Интерфейс пользователя
- •7.6. Пользовательский интерфейс приложений
- •7.7. Архитектура, управляемая событиями
- •8. Семейство операционных систем unix
- •8.1. Основные понятия системы unix
- •8.1.1. Виртуальная машина
- •8.1.2. Пользователь
- •8.1.3. Интерфейс пользователя
- •8.1.4. Привилегированный пользователь
- •8.1.5. Команды
- •8.1.6. Процессы
- •8.1.7. Выполнение процессов
- •8.1.8. Структура файловой системы
- •8.2. Операционная система Linux
- •9.1.2. Определение компилятора. Отличие компилятора от транслятора
- •9.1.3. Определение интерпретатора. Разница между интерпретаторами и трансляторами
- •9.1.4. Этапы трансляции. Общая схема работы транслятора
- •9.1.5. Понятие прохода. Многопроходные и однопроходные компиляторы
- •9.2. Таблицы идентификаторов. Организация таблиц идентификаторов
- •9.2.1. Назначение таблиц идентификаторов
- •9.2.2. Принципы организации таблиц идентификаторов
- •9.2.3. Простейшие методы построения таблиц идентификаторов
- •9.2.4. Построение таблиц идентификаторов по методу бинарного дерева
- •9.2.8. Комбинированные способы построения таблиц идентификаторов
- •9.3. Лексические анализаторы
- •9.3.1. Назначение лексического анализатора
- •9.3.2. Принципы построения лексических анализаторов
- •9.3.3. Определение границ лексем
- •9.3.4. Выполнение действий, связанных с лексемами
- •9.4. Формальные языки и грамматики
- •9.4.1. Первичные понятия
- •9.4.2. Примеры, иллюстрирующие первичные понятия
- •9.4.3. Типы формальных языков и грамматик
- •9.4.3.1. Грамматики типа 0
- •9.4.3.2. Грамматики типа 1
- •9.4.3.3. Грамматики типа 2
- •9.4.3.4. Грамматики типа 3
- •9.4.3.5. Вывод в кс-грамматиках и правила построения дерева вывода
- •9.4.3.6. Синтаксический разбор
- •9.4.3.7. Левый и правый выводы
- •9.4.3.8. Неоднозначные и эквивалентные грамматики
- •9.4.4. Способы задания схем грамматик
- •9.4.4.1. Форма Наура-Бэкуса
- •9.4.4.2. Итерационная форма
- •9.4.4.3. Синтаксические диаграммы
- •9.4.5. Построение грамматик и грамматики, описывающие основные конструкции языков программирования
- •9.4.5.1. Рекомендации по построению грамматик
- •9.4.5.2. Описание списков
- •9.4.5.3. Пример построения грамматик
- •9.4.5.4. Грамматики, описывающие целые числа без знака и идентификаторы
- •9.4.5.5. Грамматики для арифметических выражений
- •9.4.5.6. Грамматика для описаний
- •9.4.5.7. Грамматика, задающая последовательность операторов присваивания
- •9.4.5.8. Грамматики, описывающие условные операторы и операторы цикла
- •9.4.5.9. Бесскобочные выражения
- •9.4.5.10. Префиксная польская запись
- •9.4.5.11. Вычисление префиксных польских записей
- •9.4.5.12. Постфиксная польская запись
- •9.4.5.13. Вычисление постфиксных записей
- •9.5. Конечные автоматы и регулярные грамматики
- •9.6. Макроязыки и макрогенерация
- •9.6.1. Определения макрокоманд и макрогенерации
- •9.6.2. Примеры макрокоманд
- •9.6.3. Макроязыки и препроцессоры
- •Заключение
- •Библиографический список
- •Оглавление
- •394026 Воронеж, Московский просп., 14
9.3.2. Принципы построения лексических анализаторов
Лексический анализатор имеет дело с такими объектами, как константы и идентификаторы (к последним относятся и ключевые слова). Язык констант и идентификаторов является регулярным, т.е. может быть описан с помощью регулярных грамматик. Распознавателями для регулярных языков являются конечные автоматы. Следовательно, основой для реализации лексических анализаторов служат регулярные грамматики и конечные автоматы.
Задача лексического анализатора шире, чем просто проверка цепочки символов лексемы на соответствие входному языку. Кроме этого, он должен выполнить следующие действия:
определить границы лексем, которые в тексте исходной программы явно не указаны;
выполнить действия для сохранения информации об обнаруженной лексеме (или выдать сообщение об ошибке, если лексема неверна).
Эти действия связаны с определенными проблемами. Далее рассмотрено, как эти проблемы решаются в лексических анализаторах.
9.3.3. Определение границ лексем
Выделение границ лексем является нетривиальной задачей. Ведь в тексте исходной программы лексемы не ограничены никакими специальными символами. Если говорить в терминах лексического анализатора, то определение границ лексем – это выделение тех строк в общем потоке входных символов, для которых надо выполнять распознавание.
Иллюстрацией случая, когда определение границ лексемы вызывает определенные сложности, может служить пример оператора программы на языке С, имеющий вид:
k = i+++++j;
Существует только одна единственно верная трактовка этого оператора:
k = i++ + ++j;
Если явно пояснить ее с помощью скобок, то данная конструкция имеет вид:
k = (i++) + (++j);
Однако найти ее лексический анализатор может, лишь просмотрев весь оператор до конца и перебрав все варианты, причем неверные варианты могут быть обнаружены только на этапе семантического анализа (например, вариант k = (i++)++ + j; является синтаксически правильным, но семантикой языка С не допускается). Конечно, чтобы эта конструкция была в принципе допустима, входящие в нее операнды k, i и j должны быть описаны и должны допускать выполнение операций языка ++ и +.
Поэтому в большинстве компиляторов лексический и синтаксический анализаторы – это взаимосвязанные части. Возможны два принципиально различных метода организации взаимосвязи лексического и синтаксического анализа:
последовательный;
параллельный.
При последовательном варианте лексический анализатор просматривает весь текст исходной программы от начала до конца и преобразует его в таблицу лексем. Таблица лексем заполняется сразу полностью, компилятор использует ее для последующих фаз компиляции, но в дальнейшем не изменяет. Дальнейшую обработку таблицы лексем выполняют следующие фазы компиляции. Если в процессе разбора лексический анализатор не смог правильно определить тип лексемы, то считается, что исходная программа содержит ошибку.
При параллельном варианте лексический анализ текста исходной программы выполняется поэтапно, по шагам. Лексический анализатор выделяет очередную лексему в исходном коде и передает ее синтаксическому анализатору. Синтаксический анализатор, выполнив разбор очередной конструкции языка, может подтвердить правильность найденной лексемы и обратиться к лексическому анализатору за следующей лексемой либо же отвергнуть найденную лексему. Во втором случае он может проинформировать лексический анализатор о том, что надо вернуться назад к уже просмотренному ранее фрагменту исходного кода и сообщить ему дополнительную информацию о том, какого типа лексему следует ожидать. Взаимодействуя между собой таким образом, лексический и синтаксические анализаторы могут перебрать несколько возможных вариантов лексем, и если ни один из них не подойдет, будет считаться, что исходная программа содержит ошибку. Только после того, как синтаксический анализатор успешно выполнит разбор очередной конструкции исходного языка (обычно такой конструкцией является оператор исходного языка), лексический анализатор помещает найденные лексемы в таблицу лексем и в таблицу идентификаторов и продолжает разбор дальше в том же порядке.
Параллельное взаимодействие лексического и синтаксического анализаторов представим в виде схемы (рис. 30).
Исходная програм-ма |
|
Лексичес-кий анализ (сканер) |
Иденти-фикаторы |
Таблица идентифика-торов (таблица имен) |
||
|
|
|
|
|||
|
Очередная лексема |
|
Обращение за лексемой |
|||
|
|
Синтакси-ческий разбор (анализ) |
|
Рис. 30. Параллельное взаимодействие
лексического и синтаксического анализаторов
Последовательная работа лексического и синтаксического анализаторов представляет собой самый простой вариант их взаимодействия. Она проще в реализации и обеспечивает более высокую скорость работы компилятора, чем их параллельное взаимодействие. Поэтому разработчики компиляторов стремятся организовать взаимодействие лексического и синтаксического анализаторов именно таким образом.
Для большинства языков программирования границы лексем распознаются по заданным терминальным символам. Эти символы – пробелы, знаки операций, символы комментариев, а также разделители (запятые, точки с запятой и т. п.). Набор таких терминальных символов зависит от синтаксиса входного языка. Важно отметить, что знаки операций сами также являются лексемами, и необходимо не пропустить их при распознавании текста.
Последовательный вариант организации взаимодействия лексического и синтаксического анализаторов является более эффективным, так как он не требует организации сложных механизмов обмена данными и не нуждается в повторном прочтении уже разобранных лексем. Этот метод является и более простым.
Однако не для всех языков программирования возможно организовать такое взаимодействие. Это зависит в основном от синтаксиса языка, заданного его грамматикой. Большинство современных широко распространенных языков программирования, таких как С и Pascal, тем не менее, позволяют построить лексический анализ по более простому, последовательному методу.