Автор работы: Пользователь скрыл имя, 09 Декабря 2012 в 10:21, курс лекций
Временем появления на Земле вида «человек разумный» вполне можно считать тот момент, когда представители этого вида стали собирать, осмысливать, обрабатывать, хранить и передавать разнообразную информацию. Таким образом, человечество (социум) постоянно имеет дело с информацией.
Строгого научного определения понятия «информация» нет. Существует более 300 толкований этого термина.
На третьей стадии работы компилятора осуществляется построение выходного текста. Программа, реализующая эту стадию, называется генератором выходного текста (Г). Генератор каждому символу действия, поступающему на его вход, ставит в соответствие одну или несколько команд выходного языка. В качестве выходного языка могут быть использованы команды устройства, команды ассемблера, либо операторы какого-либо другого языка.
Рассмотренная схема компилятора является упрощенной, поскольку реальные компиляторы, как правило, включают стадии оптимизации.
Исходное текстовое
Размеры лексических классов различны. Например, лексический класс идентификаторов, вообще говоря, бесконечен. С другой стороны, есть лексические классы, состоящие только из одной лексемы, например, подмножество, состоящее из лексемы if. В большинстве языков программирования имеются следующие лексические классы: ключевые слова (по одному на каждое ключевое слово), идентификаторы, строковые литералы, числовые константы. Каждому подмножеству сопоставляется некоторое число, называемое идентификатором лексического класса (token) или, короче, лексическим классом.
Пример. Рассмотрим оператор языка Pascal const pi = 3.1416; Этот оператор состоит из следующих лексем:
сonst - лексический класс Const_LC
pi - лексический класс Identifier_
= - лексический класс Relation_LC
3.1416 - лексический класс Number_LC
; - лексический класс Semicolon_LC
Поскольку весьма часты ситуации, в которых более чем одна лексема принадлежит одному лексическому классу, лексический анализатор должен предоставлять дополнительную информацию о том, какая конкретная лексема была выделена. Например, в лексический класс Number_LC попадёт и строка 1, и строка 0, однако последующим этапам компилятора (скажем, кодогенератору) было бы полезно знать конкретное значение константы в исходной программе. Такую информацию можно записывать в атрибуты лексем; обычно лексема имеет только один атрибут - ссылку в некоторую таблицу с дополнительной информацией. В целях диагностики мы можем также сохранить номера строк начала и конца этой лексемы в исходной программе.
Пример. Рассмотрим следующий фрагмент программы на C#:
bool c; int a=1,b=2;
c = a>b>>2;
Последний оператор порождает следующую последовательность лексем и их атрибутов:
<Identifier_LC, указатель в таблицу на с>
<AssignOP_LC,>
<Identifier_LC, указатель в таблицу на a>
<GreaterThanOP_LC,>
<Identifier_LC, указатель в таблицу на b>
<RightShiftOP_LC,>
<Number_LC, указатель в таблицу на 2>
Будем считать, что у нас определен тип, соответствующий указателю в эту таблицу - ReprInd, и тип, служащий для представления позиции в исходном файле - FilePos. В этом случае мы можем полностью определить лексему следующим образом:
struct LEXEME {
ushort LexClass;
ReprInd ReprTabPtr;
FilePos beg;
FilePos end;
}
В таблице представлений хранится по одному экземпляру всех внешних представлений идентификаторов (и, возможно, также для всех констант). Затем идентификаторы заменяются на ссылку в эту таблицу - этот процесс называется свертыванием.
Одна из простейших форм организации таблицы - это массив указателей на строки. Однако при таком подходе замедляются два основных процесса, связанных с таблицей представлений: поиск идентификатора в таблице и добавление нового элемента. При этом поиск идентификатора в таблице является, наверное, самой массовой задачей в процессе компиляции, так как выполняется для каждого использующего вхождения идентификатора. Поэтому хотелось бы добиться максимального быстродействия для этой операции.
Поэтому более распространена другая
форма организации таблицы
Замечено, что большинство использований идентификатора находится недалеко от места его описания, поэтому рекомендуется добавлять новые идентификаторы в начало хэш-списка, а не в конец. Это повышает скорость поиска, а также упрощает поддержку реализации стандартных правил видимости в языках с блочной структурой. Например, перед входом в блок можно запоминать текущее состояние хэш-таблицы, а затем при поиске идентификатора внутри данного блока считать активным идентификатором первую переменную с данным именем, встреченную в хэш-таблице. Затем при выходе из блока необходимо восстанавливать предыдущее состояние хэш-таблицы.
Синтаксический анализ - это процесс, который определяет, принадлежит ли некоторая последовательность лексем языку, порождаемому грамматикой. В принципе, по любой грамматике можно построить синтаксический анализатор, но грамматики, используемые на практике, имеют специальную форму. Например, известно, что для любой контекстно-свободной грамматики может быть построен анализатор, сложность которого не превышает для входной строки длины , но в большинстве случаев по заданному языку программирования мы можем построить такую грамматику, которая позволит сконструировать и более быстрый анализатор. Анализаторы реально используемых языков обычно имеют линейную сложность; это достигается, например, за счёт просмотра исходной программы слева направо с заглядыванием вперед на один терминальный символ (лексический класс).
Вход синтаксического
Выход синтаксического анализатора - дерево разбора и таблицы, например, таблица идентификаторов и таблица типов, которые являются входом для следующего просмотра компилятора (например, это может быть просмотр, осуществляющий контроль типов).
Отметим, что совсем необязательно, чтобы фазы лексичекого и синтаксического анализа выделялись в отдельные просмотры. Обычно эти фазы взаимодействуют друг с другом на одном просмотре. Основной фазой такого просмотра считается фаза синтаксического анализа, при этом синтаксический анализатор обращается к лексическому анализатору каждый раз, когда у него появляется потребность в очередном терминальном символе.
(Интегрированная) среда
Обычно среда разработки включает в себя текстовый редактор, компилятор и/или интерпретатор, средства автоматизации сборки и отладчик. Иногда также содержит средства для интеграции с системами управления версиями и разнообразные инструменты для упрощения конструирования графического интерфейса пользователя. Многие современные среды разработки также включают браузер классов, инспектор объектов и диаграмму иерархии классов - для использования при объектно-ориентированной разработке ПО. При традиционном подходе, начальный этап написания программы строится следующим образом:
Исходный текст набирается при помощи какого-либо текстового редактора. По завершении набора, работа с текстовым редактором прекращается и запускается компилятор. Как правило, вновь написанная программа содержит синтаксические ошибки, и компилятор сообщает о них на консоль оператора.
Вновь запускается текстовый редактор, и оператор должен найти и устранить выявленные ошибки, при этом сообщения о характере ошибок выведенные компилятором уже не видны, так как экран занят текстовым редактором.
И этот цикл может повторяться не один раз. Если программа имеет большой объём, собирается из различных частей, и подвергается длительному редактированию или модернизации, то даже этот начальный этап может потребовать много сил и времени. После этого наступает этап отладки программы и к редактору с компилятором добавляется эмулятор или симулятор, за работой которого хотелось бы следить прямо по тексту программы в текстовом редакторе.
Избежать большого объёма однообразных действий и тем самым существенно повысить эффективность процесса разработки и отладки позволяют т.н. интегрированные среды (оболочки) разработки (Integrated Development Environment, IDE).
Работа в интегрированной среде даёт программисту:
Хотя и существуют среды разработки, предназначенные для нескольких языков — такие как Eclipse или Microsoft Visual Studio, обычно среда разработки предназначается для одного определённого языка программирования - как например, Visual Basic.
Подобные функциональные возможности, в сочетании с дружественным интерфейсом, в состоянии существенно увеличить скорость разработки программ.
К дополнительным возможностям интегрированных средств разработки следует отнести:
Профилировщик (иначе анализатор эффективности
программного кода) позволяет получить
по результатам прогона
Анализ статистической информации, поставляемой профилировщиком, позволяет легко выявлять «мёртвые» или перенапряженные участки программ, и в результате оптимизировать структуру отлаживаемой программы.
Подсистема точек останова - набор средств, управляющий процессом выполнения программы. Он позволяет останавливать выполняемую в реальном (или приближённом к реальному) масштабе времени программу, при выполнении команды, размещённой по заданному адресу. Частный случай работы системы точек останова - пошаговое выполнение. Другие, часто используемые случаи, - останов при проведении операций ввода-вывода.
Более развитый
набор сервисных функций
Процессор точек останова позволяет останавливать выполнение программы или выполнять иные действия, например, запускать или останавливать трассировщик при выполнении заданных пользователем условий. В отличие от механизма обычных точек останова, процессор точек останова позволяет формировать и отслеживать условия практически любой степени сложности, и при этом эмулируемый процесс не выводится из масштаба реального времени.
Наличие эмуляционной памяти даёт возможность использовать её в процессе отладки вместо ПЗУ в отлаживаемой системе, и более того, отлаживать программу без использования реальной системы или её макета. При необходимости внесения изменений в отлаживаемую программу достаточно загрузить новую или модифицированную программу в память эмулятора, вместо того чтобы заниматься перепрограммированием ПЗУ.
Существуют модели эмуляторов, которые позволяют пользователю «подставлять» вместо ПЗУ эмуляционную память не только целиком, но и по блочно (в некоторых моделях минимальный размер блока может достигать одного байта), в порядке, определённом пользователем. Для этого пользователю достаточно задать распределение памяти данных и памяти программ, в соответствии с которым процессор будет получать доступ и к содержимому ПЗУ в отлаживаемой системе, и к содержимому эмуляционной памяти внутрисхемного эмулятора.
Отладчик является своеобразным мостом между разработчиком и отладочным средством. Состав и объём информации, проходящей через средства ввода-вывода, доступность её для восприятия, контроля, и, при необходимости, для коррекции и модификации напрямую зависят от свойств и качества отладчика.