Системное программное обеспечение: некоторые аспекты проектирования компиляторов

УДК 004+681 ББК 32.973-018.2

Трубачева С.И.

СИСТЕМНОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ: НЕКОТОРЫЕ АСПЕКТЫ ПРОЕКТИРОВАНИЯ КОМПИЛЯТОРОВ

Trubacheva S. I.

SYSTEM SOFTWARE: SOME ASPECTS OF THE COMPILERS DESIGNING

Ключевые слова: формальное описание языка, процедура компиляции программного кода, универсальный язык моделирования, лексический анализатор.

Keywords: the formal description of the language, the procedure of compilation of program code, universal modeling language, lexical analyzer.

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

Abstract: questions of design of the analyzer compiler, modern tools of modeling of the system software are considered in the article.

Цель исследования и обоснование актуальности

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

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

Решение

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

- знаковой системы2;

- множества смыслов этой системы;

- соответствия между последовательностями знаков и смыслами, делающими «осмысленными» последовательности знаков.

Знаками могут быть буквы алфавита, математические обозначения, звуки и т. д.

Математическая лингвистика рассматривает только такие знаковые системы, в которых знаками являются символы некоторого алфавита, а последовательностями знаков — тексты, т.е. языки рассматриваются как произвольные последовательности осмысленных текстов.

1 Все языки делятся на два класса: естественные и искусственные языки. Естественные — это те, на которых мы говорим. Искусственные — созданные для работы с вычислительной техникой.

2 Знаковая система - множество допустимых последовательностей знаков.

Правила, определяющие множество текстов, образуют синтаксис языка. Описание множества смыслов и соответствия между смыслами и текстами — семантику языка. Семантика языка зависит от характера объектов, описываемых языком, и средства ее изучения различны для различных типов языков.

Математический аппарат для изучения синтаксиса языков получил название теории формальных грамматик. С точки зрения синтаксиса, язык понимается уже не как средство общения, а как множество формальных объектов — последовательностей символов алфавита.

Буква (или символ) — это неделимый знак.

Множество букв образует алфавит. Алфавиты являются множествами, и поэтому к ним можно применять теоретико-множественные обозначения.

Цепочка — упорядоченная последовательность букв алфавита. Цепочки будем называть также словами.

Альтернативным набором терминов для буквы, алфавита или цепочки (слова) является набор: слово, словарь и предложение соответственно. Совокупность цепочек (или предложений) называется языком. Формально - язык L над алфавитом А.

Систематическое использование математических методов для описания языков программирования начинается с 1960-х годов. Тогда было обнаружено, что формы Бэкуса, которые использовались для описания синтаксиса языка АЛГ0Л-60, имеют строгое формальное обоснование с помощью средств математической лингвистики.

С этого времени началась история развития и применения формального математического аппарата — теории формальных языков и грамматик — для проектирования и конструирования трансляторов3.

Процедура трансляции состоит из нескольких фаз:

-лексический анализ;

-синтаксический анализ;

-генерация промежуточного представления;

-процедура оптимизации;

-генерация кода.

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

Рассмотрим основные подходы проектирования анализатора компилятора.

В качестве средства описания модели системы рекомендуем Rational Rose Enterprise Edition. Rational Rose (RR) - CASE-средство фирмы Rational Software Corporation (США) - предназначено для автоматизации этапов анализа и проектирования ПО, а также для генерации кодов на различных языках и разработки проектной документации. RR использует синтез-методологию объектно-ориентированного анализа и проектирования (ООАиП), универсальный язык моделирования UML (Unified Modeling Language), который претендует на роль стандарта в области ООАиП. В результате разработки проекта с помощью CASE-средства Rational Rose формируются следующие документы: диаграммы прецедентов; диаграммы действий; диаграммы последовательности действий; диаграммы кооперации; диаграммы компонентов; диаграммы классов; диаграммы размещения (внедрения).

Анализатор будет спроектирован с использованием унифицированного языка моделирования (UML) и реализован на С++. Основная цель отображена на диаграмме прецедентов UML (рисунок 1).

Приступим к разработке грамматики языка [1,2,3].

В языке обязательно должны присутствовать две составляющие: синтаксис и семантика.

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

Семантика - множество правил интерпретации смысла предложения.

3 Наиболее распространенным типом трансляторов являются компиляторы.

Рисунок 1 - Диаграмма прецедентов

Отличием синтаксиса от семантики является то, что синтаксис используется при формальном построении структуры предложений.

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

Совокупность правил синтаксиса образует грамматику языка.

Поскольку алгоритмические языки в отличие от естественных языков называют формальными языками, то и грамматики этих языков также именуют формальными.

В форме Бэкуса описываются два класса объектов: это, во-первых, основные символы языка программирования и, во-вторых, имена конструкций описываемого языка, или так называемые, металингвистические переменные.

Грамматика в форме Бэкуса-Наура (БНФ) определяется следующим образом:

G (УТЖ P,S),

где:

VT - множество терминальных символов (множество символов алфавита);

"УК - множество нетерминальных символов (символов, определяющих понятия языка;

P - множество правил;

Б - целевой символ грамматики, аксиома.

С помощью БНФ разработаем входной язык, за основу возьмем конструкцию языка РЛБСЛЬ:

GRAMATICA ={ VI Ш, P S },

где:

VT =

{

<объявление переменных>,

<программа>,

<начало программы>, < конец программы>, <тело программы>, <опеpатоp безусловного пеpехода>, <структура выбора>, <ввод>, <вывод>,

<комментарий>,<выражение>,<знак сравнения>, <сумма>, <произведение>, <множитель>, <описание переменной>, <тип>, <переменная><продолжение имени>, <буква> , <пpодолжение целого>, <целое без знака><цифра>, <целое><дробь>

VN =

{

VAR, BEGIN, END, GOTO, IF, ELSE, INTEGER, READ, WRITE,TRUE, FALSE, //, /, -, +, *, <, >, !=, <=, >=, a .. z , 0 .. 9 }; p=

{

<объявление переменных>::=< описание переменной>,

<описание переменной>:: =<тип><переменная>,

<тип>:: = INTEGER\ BOOL \...,

<переменная>:: = <буква><продолжение имени>

<буква>::= а | .. | z,

<программа>::=<начало программы><тело программы> < конец программы>

<начало программы>::=BEGIN <тело программы> ...

<символ>::= а | b | ... | z | A | B | ... IZI0 | 1 | 2 | ... | 9 <комментарий>:: = //<строка>

<конец программы>::=END };

S = <описание переменной>

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

Таким образом, формальная грамматика G потенциально задает множество алгоритмов порождения языка.

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

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

Далее рассмотрим фазу лексического анализа.

На фазе лексического анализа, исходная (входная) программа, представляющая поток символов, разбивается на лексемы (слова) в соответствии с определениями языка.

В основе реализации лексического анализатора, лежат конечные автоматы и регулярные выражения.

Лексический анализ является достаточно прямолинейным процессом, который подобен процессу, автоматически производимому человеком при чтении.

Помимо распознавания символов языка, лексический анализатор выполняет следующие задачи:

- удаление комментариев;

- введение номеров строк;

- вычисление констант.

На этапе лексического анализа обнаруживаются некоторые ошибки:

- недопустимые символы;

- неправильная запись чисел.

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

Каждый дескриптор лексемы состоит из 2 частей: класса (типа) лексемы; указателя на адрес памяти, где хранится информация о конкретной лексеме.

Лексический анализ (ЛА) - это первый этап процесса компиляции. На этом этапе символы, составляющие исходную программу, группируются в отдельные лексические элементы, называемые лексемами.

Лексический анализ важен для процесса компиляции по нескольким причинам:

- замена в программе идентификаторов, констант, ограничителей и служебных слов лексемами делает представление программы более удобным для дальнейшей обработки;

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

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

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

Начнём проектирование ЛА с использованием UML, для этого идентифицируем классы. Проанализируем поставленную задачу, найдём все существительные и определим их как классы для реализации ЛА:

- файл (класс IOFile);

- лексема (класс Scanner);

- ошибка (класс ErrorLex);

- таблица служебных слов (класс TWord);

- таблица ограничителей (класс TOgranich);

- таблица идентификаторов анализируемой программы (класс TIdent);

- таблица чисел-констант, используемых в программе (класс TNum).

Так как четыре класса таблиц являются практически одинаковыми, определим ещё один класс Table для реализации наследования.

Построим диаграмму классов для исследования их взаимодействия между собой (рисунок

2).

Пояснение к диаграмме классов: сплошная линия представляет ассоциацию (отношения между классами); числа рядом с линиями выражают значение кратности. Значения кратности обозначают, сколько объектов класса участвуют в ассоциации.

При построении ЛА обычно строится диаграмма состояний (ДС) лексического разбора основанная на теории конечных автоматов (КА).

Конечный автомат задается формулой:

КА =

где:

K - конечное множество состояний;

VT - конечное множество допустимых входных символов;

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

H е K - начальное состояние;

Б е К - заключительное состояние (либо конечное множество заключительных состояний).

Рисунок 2 - Диаграмма классов

Формула F(A, 1) = В означает, что из состояния А по входному символу t происходит переход в состояние В.

Каждая дуга в ДС может выглядеть так (рисунок 3):

А 1 ц'^Ц.

Рисунок 3 - Графическая модель состояний

Если в состоянии А очередной анализируемый символ совпадает с ^ для какого-либо i = 1, 2 ,... П, то осуществляется переход в состояние В; при этом необходимо выполнить действия Dl,

Ц2, ... ,Цш.

В нашем случае мы построим диаграмму последовательностей ЦМЬ, однако перед этим необходимо идентифицировать атрибуты и операции классов (таблица 1).

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

В общем виде может быть предложен следующий порядок конструирования анализатора:

- выделить во входном языке (Ь(О)) множество классов лексем; построить для каждого класса лексем грамматику; построить модель распознавателя;

- выбрать формат и код лексем дескриптора вида - тип, адрес; построить дескрипторный текст входной программы.

Таблица 1 - Описание классов

Название класса Описание

Класс IOFile Открывает, закрывает вх./вых. файлы

Класс Scanner Выделяет лексему, берёт букву из лексемы, определяет класс лексемы.

Класс ErrorLex Выводит номер строки, тип ошибки

Класс TWord Проверяет принадлежность лексемы таблице ключевых слов

Класс TOgranich Проверяет принадлежность лексемы таблице ограничителей

Класс TIdent Проверяет принадлежность лексемы таблице идентификаторов

Класс TNum Проверяет принадлежность лексемы таблице чисел-констант

Класс Table Базовый класс

Таким образом, лексический анализатор считывает последовательность символов, выделяет из них лексемы, создает таблицы идентификаторов (таблицы лексем); организует связи этих таблиц.

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

- таких, например, как построение информационно-поисковых систем.

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

1. Дейтел, X.M., Дейтел, П.Дж.. Как программировать на С++. - М.: БИНОМ, 2010.

2. Ахо, А., Ульман, Дж. Сети. Компиляторы. Принципы, технологии, инструменты. - Т. 1,2. - М.: Мир, 2009.

3. Вайнгартен, Ф. Трансляция языков программирования. - М.: Мир, 2004.

4. Братчиков, И.Л.. Синтаксис языков программирования. - М.: Наука, 2005.

5. Гинзбург, С. Математическая теория контекстно-свободных языков. - М.: Мир, 2000.