МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное бюджетное образовательное учреждение

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное бюджетное образовательное учреждение
высшего профессионального образования
«КУБАНСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ»
(ФГБОУ ВПО «КубГУ»)
Кафедра вычислительных технологий
РАЗРАБОТКА JIT-КОМПИЛЯТОРА ДЛЯ ПРОГРАММНОЙ
ПЛАТФОРМЫ МОДЕЛИРОВАНИЯ ИНФОРМАЦИОННЫХ
ПРОЦЕССОВ
Храмцова Виктория Викторовна
Краснодар 2014
Содержание
Введение ................................................................................................................... 4
Обзор предметной области..................................................................................... 6
Имитационное моделирование ........................................................................... 6
Использование графов в моделировании ........................................................ 13
Методы анализа свойств (характеристик) системы ....................................... 15
Трансляторы ....................................................................................................... 18
JIT-компиляция .................................................................................................. 22
Постановка задачи................................................................................................. 24
Разработка jit-компилятора ............................................................................... 24
Инструменты реализации проекта ................................................................... 24
Реализация.............................................................................................................. 25
Характеристика языка моделирования ............................................................ 25
Описание грамматики входного языка ........................................................... 31
Общая архитектура jit-компилятора ................................................................ 32
Подсистема анализа ........................................................................................... 33
Лексический анализатор ................................................................................ 33
Синтаксический анализатор .......................................................................... 38
Семантический анализатор ............................................................................ 45
Подсистема синтеза ........................................................................................... 47
Генерация кода ................................................................................................ 47
Внутреннее представление объектов моделирования ................................ 52
Заключение ............................................................................................................ 59
Список использованных источников .................................................................. 60
Приложения ........................................................................................................... 63
Приложение 1 ..................................................................................................... 63
Приложение 2 ..................................................................................................... 65
Приложение 3 .................................................................................................... 71
Приложение 4 .................................................................................................... 72
Введение
В
различных
областях
человеческой
деятельности
широко
используются математические методы и ЭВМ. Создаются и развиваются
математические модели различных объектов и явлений, а также методы,
исследующие эти модели. Математическая модель — это приближенное
описание какого-либо класса явлений или объектов реального мира на языке
математики. Основная цель моделирования — исследовать эти объекты и
предсказать
результаты
будущих
наблюдений.
Частным
случаем
математического моделирования является имитационное моделирование. К
такому типу моделирования обращаются в том случае, когда натурный
эксперимент невозможен или затруднен по тем или иным причинам.
Исследование
структурных
свойств
компьютерных
сетей
алгоритмическими методами или методами имитационного моделирования
предполагает возможность описания сетей как структур данных, т.е.
объектов некоторого языка. Дискретные структуры при этом могут быть
представлены такими математическими объектами, как обыкновенные и
ориентированные графы, графы с полюсами [1], гиперграфы, иерархические
структуры, случайные графы [2, 3].
Программная платформа исследования структур компьютерных сетей
включает собственный язык [1] для описания объектов моделирования.
Проблема разработки транслятора языка моделирования обладает рядом
особенностей.
Транслятор – это программа, которая преобразует программу,
написанную на исходном языке программирования, в рабочую программу,
представленную
выполняет
на
объектном
следующие
задачи:
(целевом)
анализ
языке.
Любой
транслируемой
транслятор
программы;
диагностика ошибок; формирование таблиц идентификаторов; генерация
объектного кода входной программы; распределение памяти для выхода.
Трансляторы делятся на два вида – компиляторы и интерпретаторы.
Разница между ними заключается в том, что интерпретатор пооператорно
выполняет команды исходной программы, а компилятор осуществляет
трансляцию программы на исходном языке программирования высокого
уровня в эквивалентную программу на низкоуровневом языке, близком к
машинному коду.
Цель настоящей работы состоит в разработке jit-компилятора входного
языка для программной платформы моделирования информационных
процессов. Архитектура компилятора состоит из традиционных [4, 5] частей:
лексического, синтаксического, семантического анализаторов, объединенных
в подсистему анализа, и генератора кода и run – time системы.
Сгенерированный код сохраняется в оперативной памяти компьютера для
последующего исполнения.
Обзор предметной области
Большие системы состоят из огромного количества элементов и еще
большего
количества
охарактеризовать
связей
между
неоднородностью
ними.
элементов
Такие
системы
(большим
можно
количеством
различных типов элементов) и неоднородностью связей. Из этого можно
сделать вывод, что для описания и анализа больших систем классические
математические методы использовать практически невозможно.
В качестве примера большой системы можно привести современный
процессор или глобальную компьютерную сеть. Несмотря на то, что
отдельные элементы или связи прекрасно описываются моделями дискретной
математики или теории массового обслуживания, о системе в целом этого
сказать нельзя. Именно поэтому необходимо найти некую альтернативу. В
данном случае ею является использование имитационного моделирования,
позволяющего соединить между собой разнородные математические модели
элементов.
Имитационное моделирование
Моделирование – это воспроизведение образа реального объекта или
процесса в виде его модели.
Моделирование – важнейший метод исследования информационных
процессов, который заключается в создании математической модели
исследуемого процесса и проведении анализа модели вместо анализа самого
процесса[6].
Основания для использования модели:
1) Отсутствие
реализации
информационной
системы
на
момент
проведения анализа.
2) В результате действий по исследованию объекта могут появиться
факторы, приводящие к полному или частичному нарушению
функционирования этого объекта, что является недопустимым, а также
результат исследования становится недостоверным.
3) Разнообразие средств экспериментального исследования реального
объекта значительно беднее разнообразия средств теоретического и
компьютерного исследования.
Наиболее
важными
типами
моделей
для
исследования
информационных процессов являются математические и алгоритмические
модели.
Искусство моделирования состоит в умении выделить минимальное
количество существенных характеристик (параметров) объекта, которые
легко непосредственно определить и связать их известной хорошо
разработанной
математической
схемой
с
интересующими
нас
характеристиками.
В настоящее время компьютерное моделирование является одним из
основных видов моделирования.
Компьютерное моделирование – это метод решения задачи анализа или
синтеза сложной системы на основе использования ее компьютерной модели.
Суть компьютерного моделирования заключена в получении количественных
и качественных результатов на основе имеющейся модели.
Под компьютерной моделью понимают:

Условный образ объекта или некоторой системы, описанный с
помощью
диаграмм,
взаимосвязанных
графиков,
компьютерных
рисунков,
таблиц,
анимационных
блок-схем,
фрагментов,
гипертекстов и т.д. и отображающий структуру и взаимосвязи между
элементами объекта – структурно-функциональная модель;

Отдельную
программу,
совокупность
программ,
программный
комплекс, позволяющий с помощью последовательности вычислений и
графического отображения их результатов воспроизводить процессы
функционирования
объекта
при
условии
воздействия
на
него
различных факторов – имитационные модели.
Компьютерное моделирование имеет ряд преимуществ по сравнению с
другими подходами. В частности, оно дает возможность учитывать большое
количество переменных, предсказывать развитие нелинейных процессов,
возникновение синергетических эффектов. Компьютерное моделирование
позволяет не только получить прогноз, но и определить, какие управляющие
воздействия приведут к наиболее благоприятному развитию событий.
Качественные выводы, сделанные по результатам компьютерного
моделирования, позволяют обнаружить такие свойства сложной системы, как
ее структуру, динамику развития, устойчивость, целостность и др..
Количественные выводы в основном носят характер прогноза некоторых
будущих или объяснения прошлых значений переменных, характеризующих
систему. Одно из основных направлений использования компьютерного
моделирования – поиск оптимальных вариантов внешнего воздействия на
объект с целью получения наивысших показателей его функционирования.
Компьютерное моделирование – эффективный метод решения задач
анализа
и
синтеза
сложных
систем.
Методологической
основой
компьютерного моделирования является системный анализ (в то время, как у
моделирования на ЭВМ – те или иные разделы теории математических
моделей), – именно поэтому в ряде источников наряду с термином
«компьютерное» используется термин системного моделирования, а саму
технологию системного моделирования призваны осваивать системные
аналитики.
Однако, ситуацию не стоит представлять так, что традиционные виды
моделирования
Наоборот,
противопоставляются
доминирующей
компьютерному
тенденцией
моделированию.
сегодня
является
взаимопроникновение всех видов моделирования, симбиоз различных
информационных технологий в области моделирования, особенно для
сложных приложений и комплексных проектов по моделированию. Так,
например, имитационное моделирование включает в себя:

концептуальное моделирование (на ранних этапах формирования
имитационной модели);

логико-математическое (включая методы искусственного интеллекта)
для целей описания отдельных подсистем модели, а также в
процедурах
обработки
и
анализа
результатов
вычислительного
эксперимента и принятия решений;

технология проведения, планирования вычислительного эксперимента
с
соответствующими
имитационное
математическими
моделирование
из
методами
привнесена
физического
в
(натурного)
моделирования;

структурно-функциональное
создании
моделирование
стратифицированного
используется
описания
при
многомодельных
комплексов.
Становление
компьютерного
моделирования
связано
с
имитационным моделированием.
Имитационное моделирование – один из видов компьютерного
моделирования,
использующий
методологию
системного
анализа,
центральной процедурой которого является построение обобщенной модели,
отражающей все факторы реальной системы, в качестве же методологии
исследования выступает вычислительный эксперимент.
Имитационная модель строится строго целенаправленно, поэтому для
нее характерно адекватное отображение исследуемого объекта, логикоматематическая
модель
системы
представляет
собой
программно
реализованный алгоритм функционирования системы. При имитационном
моделировании структура моделируемой системы адекватно отображается в
модели, а процесс ее функционирования имитируется на построенной
модели. Под имитацией понимают проведение на компьютерах различных
серий экспериментов с моделями, которые представлены в качестве
некоторого
набора
(комплекса)
характеристик
(конструкций,
осуществляется
путем
компьютерных
управлений)
вариантных
программ.
Сравнение
моделируемого
просчетов.
Особую
роль
объекта
имеет
возможность многократного воспроизведения моделируемых процессов с
последующей их статистической обработкой, позволяющая учитывать
случайные
внешние
воздействия
на
изучаемый
объект.
На основе
набираемой в ходе компьютерных экспериментов статистики делаются
выводы в пользу того или иного варианта функционирования или
конструкции реального объекта или сущности явления.
В ряде случаев формировать решения с помощью формальных
методов не удается – эксперт должен быть включен в процесс принятия
решения.
Он
становится
активным
компонентом
информационной
системы; детализирует проблему и модель, осуществляет постановку
направленного вычислительного эксперимента на модели, генерацию и
ранжирование альтернатив, выбор критериев для принятия решений, а также
формирует рациональный вариант управления с помощью базы знаний.
Принятие решений в условиях риска, например, требует ведения диалоговых
процедур
формирования
статистически
достоверных
результатов
и
поэтапного сопоставления их с функцией цены риска. Необходимо
осуществлять прямое участие эксперта в формировании оптимального
множества вариантов решений и в процедурах вариантного синтеза.
Таким
образом,
имитационное
моделирование
значительно
расширяет возможности и эффективность работы лиц, принимающих
решения (ЛПР), предоставляя им удобный инструмент и средства для
достижения поставленных целей. Имитационное моделирование реализует
итерационный характер разработки модели системы, поэтапный характер
детализации
моделируемых
подсистем,
что
позволяет
постепенно
увеличивать полноту оценки принимаемых решений по мере выявления
новых проблем и получения новой информации.
Имитационная модель не дает оптимального решения подобно
классическому решению задач оптимизации, но она является удобным для
системного аналитика вспомогательным средством для поиска решения
определенной проблемы. Область применения имитационных моделей
практически не ограничена, это могут быть задачи: исследования структур
сложных систем и их динамики, анализа узких мест, прогнозирования и
планирования и т.д. Главным преимуществом имитационного моделирования
является то, что эксперт может ответить на вопрос: «Что будет, если … », т.е.
с помощью эксперимента на модели вырабатывать стратегию развития.
В последнее время ведутся работы по разработке систем, способных
оказать помощь эксперту при ответе на обратный вопрос «Что надо, чтобы
…». Это можно назвать как «целевое моделирование», при котором на вход
системы подаются показатели целевого состояния, а также перечень
возможных регуляторов с указанием диапазона и шага их изменения.
Система в автоматическом или полуавтоматическом режиме находит
сочетание значений этих регуляторов для достижения заданного целевого
состояния.
Итак,
преимущества
системно-динамического
моделирования
заключаются в следующем: системно-динамический подход начинается с
попытки понять ту систему причин, которая породила проблему и
продолжает поддерживать ее. Для этого собираются необходимые данные из
различных источников, включая литературу, информированных людей
(менеджеров,
потребителей,
конкурентов,
экспертов)
и
проводятся
специальные количественные исследования. После того как элементарный
анализ причин проблемы произведен, формальная модель считается
построенной. Первоначально она представляется в виде логических
диаграмм, отражающих причинно-следственные связи, которые затем
преобразуются в сетевую модель, изображенную, например, графическими
средствами системы “Ithink”. Затем эта сетевая модель автоматически
преобразуется в ее математический аналог – систему уравнений, которая
решается численными методами, встроенными в систему моделирования.
Полученное решение представляется в виде графиков и таблиц, которые
подвергаются критическому анализу. В результате модель пересматривается
(изменяются параметры некоторых узлов сети, добавляются новые узлы,
устанавливаются новые или изменяются существовавшие ранее связи и т.д.),
затем модель вновь анализируется и так до тех пор, пока она не станет в
достаточной мере соответствовать реальной ситуации. После того как модель
построена, в ней выделяются управляемые параметры и выбираются такие
значения этих параметров, при которых проблема либо снимается, либо
перестает быть критически важной.
В процессе моделирования постепенно углубляется понимание
проблемы участвующими в нем людьми. Однако их интуиция о возможных
последствиях предлагаемых управленческих решений часто оказывается
менее надежной, чем подход, связанный с тщательным построением
математической модели. И это не так удивительно, как может показаться на
первый взгляд. Системы управления содержат порой 100 и более
переменных, о которых либо известно, что они зависят от других каким-либо
нелинейным образом или предполагают существование такой зависимости.
Поведение таких систем оказывается настолько сложным, что его понимание
лежит
вне
возможностей
человеческой
интуиции.
Компьютерное
моделирование – одно из наиболее эффективных имеющихся в настоящее
время средств для поддержки и уточнения человеческой интуиции. Хотя
модель и не является совершенно точным представлением реальности, она
может быть использована для принятия более обоснованных решений, чем
те, которые мог бы принять человек. Это гибкое средство, которое усиливает
возможности человека, использующего ее для более глубокого понимания
проблемы.
Таким образом, в сфере современных информационных технологий
имитационное
моделирование
приобретает
в
мировых
научных
исследованиях и практической деятельности крайне весомое значение. С
помощью имитационного моделирования эффективно решаются задачи
самой широкой проблематики:

в области стратегического планирования;

бизнес-моделирования;

менеджмента (моделирование различного рода финансовых проектов,
управление производством);

реинжиниринга;

проектирования (актуально применение имитационного моделирования
в области инвестиционно-технологического проектирования, а также
моделирования
и
прогнозирования
социально-экономического
развития региональных и городских систем).
Использование графов в моделировании
В системе моделирования информационных процессов объектами
имитирования являются:
1. вычислительные системы и их компоненты, в т.ч. процессоры,
элементы памяти, цифровые электронные схемы, соединения между
ними;
2. компьютерные сети, их узлы, линии связи (проводной и беспроводной).
Обычно такие объекты изображаются следующим образом:
Рисунок 1
На рисунке 1 можно отметить, что тем или иным образом блоки
моделируемого объекта связаны между собой. Кроме того, очевидно, что
таких связей достаточно много.
Цифровые микросхемы (рис. 2) работают с логическими сигналами,
имеющими два разрешенных уровня напряжения (уровень 0 и уровень 1).
Рисунок 2
При классической организации связей (рис. 3) все сигналы между
устройствами передаются по своим отдельным линиям. Каждое устройство
передает свои сигналы всем другим независимо от других устройств. В этом
случае обычно получается очень много линий связи, к тому же правила
обмена сигналами по этим линиям чрезвычайно разнообразны.
Рисунок 3
Нетрудно заметить, что моделируемые объекты обладают структурой,
т.е. в них можно выделить элементы (блоки, узлы) и связи между элементами.
Среди известных математических конструкций наиболее подходящей
выглядит граф. Вершина графа представляет собой элемент (блок, узел), а
ребро или дуга графа – связь (проводник, шина).
Т.к. связей между блоками может быть немало, то у вершин нужно
выделять входы и выходы (полюсы). Соединяются не вершина с вершиной, а
полюс с полюсом через вспомогательные элементы. Т.о., получаем новые
структуры – P-графы (рис. 4). В P-графе вершина представляет собой
множество полюсов. Ребро или дуга P-графа – это пара полюсов разных
вершин. P-граф также имеет полюсы ().
Рисунок 4
На выходах элементов системы возникают сигналы, которые по связям
передаются на входы других элементов.
Сигнал, пришедший на вход элемента, приводит к каким-то изменениям
в этом элементе и, возможно, к появлению сигналов на выходе(-ах) этого
элемента.
Разработка разных архитектур вычислительных систем производится
для того, чтобы «обмануть» задержки, вызванные изменениями состояния
между входным и выходным сигналом.
Методы анализа свойств (характеристик) системы
Одной из важных проблем в области разработки и создания
современных сложных технических систем является исследование динамики
их функционирования на различных этапах проектирования, испытания и
эксплуатации. Сложными системами называются системы, состоящие из
большого числа взаимосвязанных и взаимодействующих между собой
элементов.
При
исследовании
сложных
систем
возникают
задачи
исследования как отдельных видов оборудования и аппаратуры, входящих в
систему, так и системы в целом.
К
разряду
сложных
систем
относятся
крупные
технические,
технологические, энергетические и производственные комплексы.
Натуральные испытания сложных систем связаны с большими затратами
времени и средств. Проведение испытаний предполагает наличие готового
образца системы или ее физической модели, что исключает или затрудняет
использование этого метода на этапе проектирования системы.
Широкое применение для исследования характеристик сложных систем
находит метод полунатурального моделирования. При этом используется
часть реальных устройств системы. Включенная в такую полунатуральную
модель
ЭВМ
имитирует
работы
остальных
устройств
системы,
отображенных математическими моделями. Однако в большинстве случаев
этот метод также связан со значительными затратами и трудностями, в
частности, аппаратной стыковкой натуральных частей с ЭВМ.
Исследование
функционирования
сложных
систем
с
помощью
моделирования их работы на ЭВМ помогает сократить время и средства на
разработку.
Затраты рабочего времени и материальных средств на реализацию
метода имитационного моделирования оказываются незначительными по
сравнению с затратами, связанными с натурным экспериментом. Результаты
моделирования по своей ценности для практического решения задач часто
близки к результатам натурного эксперимента.
Любая синтезированная или определенная каким-либо другим образом
структура сложной системы для оценки ее показателей должна быть
подвергнута испытаниям. Проведение испытаний системы является задачей
анализа ее характеристик. Таким образом, конечным этапом проектирования
сложной системы, осуществленного как методом синтеза структуры, так и
методом
анализа
вариантов
структур,
является
анализ
показателей
эффективности проектируемой системы.
Среди известных методов анализа показателей эффективности систем и
исследования динамики их функционирования следует отметить:
1. Алгоритмический (статический) анализ (рис. 5). Описание модели
производится на языке моделирования – языке высокого уровня. Оно
должно быть переведено в структуры данных и машинные команды.
Такой анализ позволяет оценить структурные характеристики моделей:
связность компьютерной сети; нахождение мостов; маршрутов;
вычислить остовное дерево; получить ярусно-параллельную форму и
т.д.
Рисунок 5
2. Имитационный (динамический) анализ off-line (рис. 6). Симулятор
играет
роль
главной
программы,
работающей
с
машинным
представлением модели, своего рода супервизор ОС. Такой метод
позволяет провести наиболее полный анализ того, как характеристики
компьютера или сети изменяются с течением времени, оценить
производительность системы, найти «узкие места», и оптимизировать
вычислительные процессы.Недостаток – хранение данных большого
объема.
Рисунок 6
3. Имитационный
(динамический)
анализ
on-line
(рис.
7).
Информационные процедуры ведут обработку в сеансе имитации и,
поэтому, не создаются большие объемы данных.
Рисунок 7
Трансляторы
Поскольку текст, записанный на языке программирования, непонятен
компьютеру, то требуется перевести его на машинный код. Такой перевод
программы с языка программирования на язык машинных кодов называется
трансляцией,
а
выполняется
она
специальными
программами
–
трансляторами.
Транслятор - обслуживающая программа, преобразующая исходную
программу, предоставленную на входном языке программирования, в
рабочую программу, представленную на объектном языке.
В настоящее время трансляторы разделяются на две основные группы:
компиляторы и интерпретаторы.
Любой транслятор выполняет следующие основные задачи:

анализирует транслируемую программу, в частности определяет,
содержит ли она синтаксические ошибки;

генерирует выходную программу (ее часто называют объектной) на
языке машинных команд;

распределяет память для объектной программы.
Интерпретатор (рис. 8) - программа или устройство, осуществляющее
пооператорную трансляцию и выполнение исходной программы. В отличие
от компилятора, интерпретатор не порождает на выходе программу на
машинном языке. Распознав команду исходного языка, он тут же выполняет
ее. Как в компиляторах, так и в интерпретаторах используются одинаковые
методы анализа исходного текста программы. Но интерпретатор позволяет
начать обработку данных после написания даже одной команды. Это делает
процесс разработки и отладки программ более гибким. Кроме того,
отсутствие выходного машинного кода позволяет не "захламлять" внешние
устройства дополнительными файлами, а сам интерпретатор можно
достаточно
легко
адаптировать
к
любым
машинным
архитектурам,
разработав его только один раз на широко распространенном языке
программирования. Поэтому, интерпретируемые языки, типа JavaScript, VB
Script, получили широкое распространение. Недостатком интерпретаторов
является низкая скорость выполнения программ. Обычно интерпретируемые
программы выполняются в 50-100 раз медленнее программ, написанных в
машинных кодах.
Рисунок 8
Компилятор (рис. 9) - это обслуживающая программа, выполняющая
трансляцию на машинный язык программы, записанной на исходном языке
программирования. Компилятор обеспечивает преобразование программы с
одного языка на другой (чаще всего, в язык конкретного компьютера).
Вместе с тем, команды исходного языка значительно отличаются по
организации и мощности от команд машинного языка. Существуют языки, в
которых одна команда исходного языка транслируется в 7-10 машинных
команд. Однако есть и такие языки, в которых каждой команде может
соответствовать 100 и более машинных команд (например, Пролог). Кроме
того, в исходных языках достаточно часто используется строгая типизация
данных,
осуществляемая
через
их
предварительное
описание.
Программирование может опираться не на кодирование алгоритма, а на
тщательное обдумывание структур данных или классов. Процесс трансляции
с таких языков обычно называется компиляцией, а исходные языки обычно
относятся
к
языкам
программирования
высокого
уровня
(или
высокоуровневым языкам). Абстрагирование языка программирования от
системы команд компьютера привело к независимому созданию самых
разнообразных языков, ориентированных на решение конкретных задач.
Появились языки для научных расчетов, экономических расчетов, доступа к
базам данных и другие.
Рисунок 9
Компиляция состоит из двух частей: анализа и синтеза.
Анализ — это разбиение исходной программы на части и создание ее
промежуточного представления.
Синтез — конструирование требуемой целевой программы из
промежуточного представления.
В процессе анализа определяются и записываются в иерархическую
древовидную структуру операции, заданные исходной программой. Часто
используется специальный вид дерева, называемый деревом синтаксического
разбора. В нем каждый узел представляет операцию, а его потомки —
аргументы операции.
При компиляции анализ состоит из трех фаз:
1. Лексический анализ. При лексическом анализе поток символов исходной
программы считывается слева направо и группируется в токены (token),
представляющие собой последовательности символов с определенным
совокупным значением.
2. Синтаксический анализ. Во время синтаксического анализа токены
иерархически группируются во вложенные конструкции с совокупным
значением.
3. Семантический анализ. Во время семантического анализа проверяется,
насколько корректно совместное размещение компонентов программы.
Разделение анализа на лексический и синтаксический достаточно
произвольно. Обычно оно используется для упрощения анализа в целом.
Одним
из
факторов,
определяющих
данное
разбиение,
является
использование рекурсии в правилах анализа. Лексические конструкции не
требуют рекурсии, в то время как синтаксические редко обходятся без нее,
поскольку
иерархическая
структура
программы
обычно
выражается
рекурсивными правилами.
В
процессе
семантического
анализа
проверяется
наличие
семантических ошибок в исходной программе и накапливается информация
для следующей стадии — генерации кода. При семантическом анализе
используются
иерархические
структуры,
полученные
во
время
синтаксического анализа для идентификации операторов и операндов
выражений и инструкций.
Важным аспектом семантического анализа является проверка типов,
когда компилятор проверяет, что каждый оператор имеет операнды
допустимого спецификациями языка типа. Например, определение многих
языков
программирования
требует,
чтобы
при
использовании
действительного числа в качестве индекса массива приводило к генерации
ошибки.
Синтезирующая часть из промежуточного представления создает
новую программу, которую компьютер в состоянии понять. Такая программа
называется объектной программой.
В качестве промежуточного представления обычно используются
деревья, в частности, так называемые деревья разбора.
JIT-компиляция
Процесс компиляции некоторых программ отличается от процесса
компиляции
статически
компилируемых
языков
программирования.
Статический компилятор преобразует исходный код непосредственно в
машинные инструкции, которые могут быть выполнены на целевой
платформе.
Различные
аппаратные
платформы
требуют
применения
различных компиляторов.
Способ получения переносимой (portable) объектной программы связан
с использованием виртуальных машин (virtual machine) .
При таком подходе исходный язык транслируется в коды некоторой
специально
разработанной
машины,
которую
никто
не
собирается
реализовывать "в железе".
Затем для каждой целевой платформы пишется интерпретатор
виртуальной машины.
Одна из первых виртуальных машин была разработана в 70-х годах XX
века Н. Виртом при написании компилятора Pascal-P. Этот компилятор
генерировал специальный
P-код
–
последовательность инструкций
гипотетической стековой машины. Подобный подход используется в языке
Java: компиляторы генерируют байт-код – последовательность команд
виртуальной Java-машины.
Java-компилятор преобразует исходный Java-код в переносимые байткоды JVM, которые являются для JVM "инструкциями виртуальной
машины". В отличие от статических компиляторов javac выполняет очень
маленькую
оптимизацию
-
оптимизация,
проводимая
статическими
компиляторами, выполняется во время исполнения программы.
Первые поколения JVM были полностью интерпретируемыми. JVM
интерпретировала байт-код вместо компиляции его в машинный код и
выполнения машинного кода. Этот подход, естественно, не предлагал
наилучшей возможной производительности, поскольку система больше
времени тратила на выполнение интерпретатора, а не самой программы.
Строго определенная JIT-виртуальная машина перед выполнением
преобразовывает все байт-коды в машинный код, но делает это в
неторопливом стиле: JIT компилирует code path только тогда, когда знает,
что code path будет сейчас выполняться (отсюда и название just-in-time
компиляция). Этот подход дает возможность программам стартовать
быстрее, поскольку не требуется длительная фаза компиляции перед
возможным началом исполнения кода.
JIT-компиляция устраняет накладные расходы интерпретации (за счет
некоторого дополнительного замедления запуска), но уровень оптимизации
кода по некоторым причинам был посредственным. С технической точки
зрения JIT-виртуальная машина компилирует каждый байт-код перед его
выполнением. Термин JIT часто используется для обозначения любой
динамической компиляции байт-кода в машинный код, даже когда возможна
интерпретация байт-кода.
Постановка задачи
Разработка jit-компилятора
Задание состоит в написании на языке С, отладке и тестировании
программного комплекса «Транслятор языка моделирования », который
состоит из трех программных модулей:
1. модели;
2. структуры;
3. процессы.
Программный комплекс получает на входе текстовый файл с описанием
модели, в состав которой входят структуры и процессы, на языке . Файл
может
содержать
синтаксического
ошибки.
анализа
Задачей
комплекса
(использовать
является
алгоритмы
проведение
нисходящего
синтаксического анализа LL(k)), семантического анализа и генерации кода
(для архитектуры IA32). Метод трансляции – JIT, т.е. результат трансляции
(код и различные таблицы) остается в оперативной памяти компьютера для
последующего исполнения. Исполнение созданного кода должно приводить
к построению в оперативной памяти внутреннего спискового представления
модели в виде совокупности связанных дескрипторов.
Инструменты реализации проекта
В
качестве
языка
реализации
транслятора
был
выбран
язык
программирования высокого уровня C++. Наиболее популярной средой
разработки на языке программирования C++ в операционной системе
Windows является Microsoft Visual Studio. В данной работе используется
тринадцатая версия такой среды разработки.
Реализация
Характеристика языка моделирования
Язык
моделирования
µ
является
языком
программирования
императивного типа. Это означает, что описывается процесс вычислений
посредством описания управляющей логики программы, т.е. в виде
последовательности
отдельных
команд,
которые
должен
выполнить
компьютер. Каждая команда является инструкцией, изменяющей состояние
программы. Программа, написанная в императивном стиле, похожа на набор
приказов, выражаемых повелительным наклонением в естественных языках.
Машинный
реализации
код
этой
является
парадигмы:
наиболее
состояние
низкоуровневым
программы
примером
определяется
содержимым памяти, а команды — инструкциями машинного кода.
Поскольку эта парадигма естественна для человеческого понимания и
непосредственно реализована на аппаратном уровне, большинство языков
программирования придерживаются именно ее.
Императивное
программирование
является
противоположностью
декларативного программирования; второе описывает, что необходимо
сделать, а первое — как именно это сделать.
Основной структурой представления данных языка µ являются Pграфы.
Язык моделирования содержит
три основных конструкции для
моделирования вычислительных систем: <описание структуры> (structure),
<описание процесса> (process) и <описание модели> (model). Структуры и
процессы описываются внутри блока описания модели. В программе могут
вводиться переменные и константы. Константы задаются выделенными
идентификаторами.
совпадающими
с
Переменные
задаются
обозначениями
идентификаторами,
констант.
Для
не
переменных
предусматривается конструкция языка <описание переменных>.
Для основных конструкций, таких как: <описание структуры>,
<описание процесса> и <описание модели> предусматриваются: оператор
присваивания, условный оператор, оператор цикла со счетчиком. Для
организации вычислений предусматриваются вспомогательные типы данных
– целый (short, int, long) и вещественный (float, double). Запись бинарных
операций является инфиксной, операции могут объединяться в выражения.
Предусмотрены
бинарные
операторы
двух
приоритетов.
Бинарные
операторы приоритета 1: +; -; ==; <>; <-; ->; =/=. Бинарные операторы
приоритета 2: *; **; \.
Кроме
того,
для
каждой
из
основных
конструкций
имеются
индивидуальные операторы и константы.
В структурах предусмотрен определенный набор графовых констант:
1. compl — полный граф;
2. nc — несвязный граф;
3. path — путь;
4. cycle — ориентированный цикл;
5. rectan — решетка;
6. star — звезда;
7. bipart — двудольный граф;
8. tree — дерево;
9. dpath
10. dcycle
11. drectan
12. dstar
13. dbipart
14. dtree
А также реализован оператор задания полюса. В нем описываются
спецификаторы полюсов (input, output, polus) и происходит сопоставление
имен вершин, которые содержат такие полюсы. Пример использования
оператора задания полюса представлен в таблице 1:
Таблица 1
structure M(input X; output Y) def
M = compl(5)(a, b, c, d, e);
X = (a);
Y = (d,e)
endstr
Как видно из примера, в описании структуры в качестве параметров
заданы два полюса X и Y, которые являются выходом и выходом
соответственно. В теле построения структуры задана графовая константа
типа полный граф, которая состоит из пяти вершин. С помощью оператора
задания полюса, вершине a сопоставляется полюс входа, а вершинам d и e
полюсы выхода. Также стоит иметь в виду, что у каждой из вершин может
быть несколько полюсов.
В
общем
случае
структуры
предназначены
для
реализации
конструкций языка моделирования, предназначенных для описания графов.
Пример описания структуры (рис. 10):
structure CommonBase def
CommonBase = path(2)(Xeon, Ram64);
endstr
Рисунок 10
В результате трансляции такого описания структуры создается машинный
код (команды и структуры данных), который можно считать объектным
кодом некоторой функции (процедуры), возвращающей после вызова в
качестве значения ссылку на структуру. Внутри этого кода, в свою очередь,
находятся вызовы других функций, например, вызов функции path.
Если в основной программе (программе анализа) описана переменная:
ref structure C;
то оператор
C = CommonBase;
при выполнении программы приводит к вызову объектного кода функции.
Эта функция создает в памяти внутреннее представление структуры,
состоящее из взаимосвязанных дескрипторов. В данном примере создается:
1) дескриптор графа (структуры);
2) два дескриптора вершин;
3) дескриптор ребра графа;
4) вспомогательные дескрипторы.
Дополнительные примеры структур систем, сетей и их описания
представлены в приложении 1.
Графы описывают статику моделируемой вычислительной системы, т.е. те
свойства, которые не зависят от времени. Но в системе постоянно происходят
изменения, т.е. система динамична. Для описания динамики используются
процессы.
С каждой вершиной графа (структуры) модели может быть связан
процесс. Он называется элементарным последовательным процессом,
поскольку состоит из событий, которые происходят одно за другим в разные
моменты времени. Поскольку вершины графа связаны между собой, то и
элементарные процессы разных вершин взаимодействуют. Множество
взаимодействующих элементарных последовательных процессов образуют
последовательный процесс модели.
Процессы протекают во времени. События «происходят» в некоторые
моменты времени. Событие в процессе имеет две характеристики:
1) что происходит – как изменяется состояние процесса, значения
связанных с ним переменных;
2) когда происходит – момент модельного времени, в который
происходят эти изменения (дискретно, мгновенно, скачком).
Именно поэтому для процессов предусмотрены следующие операторы:

оператор планирования события;

оператор передачи сообщения;

оператор вывода.
Оператор планирования события позволяет определить, в какой момент
времени должно произойти событие. Пример использования описанного
оператора (табл. 2):
Таблица 2
process ThreeEvents
initial let E0 be in 0.0 endi
event E0; write (“старт”); let E1 be in 2.4 ende
event E1; let E2 be in 5.6 ende
event E2; write (“СТОП”) ende
endpro.
Вот что описывает приведенный выше код программы: событие E0
происходит в момент времени t = 0; событие E1 – в момент t = 2.4; событие
E2 – в момент t = 8.0; это момент окончания процесса.
Оператор передачи сообщения обеспечивает передачу сообщения
внутри модели по связям (дугам и ребрам). Пример использования
представлен в таблице 3:
Таблица 3
event GenerateMessage;
k = k + 1;
out k via R, S;
let GenerateMessage be in k + 10;
ende
Оператор вывода обеспечивает передачу данных из модели во
внешнюю среду, например, в выходной файл, на экран монитора, в базу
данных. Пример: write (“СТОП”).
В результате трансляции описания процесса создается машинный код
(команды и структуры данных), который можно считать объектным кодом
некоторой функции (процедуры), возвращающей после вызова в качестве
значения ссылку на процесс.
Внутри этого кода, в свою очередь, находятся вызовы других функций.
Эти функции создают в памяти внутреннее представление процесса,
состоящее из взаимосвязанных дескрипторов и сегментов кода. Создаются:
1) дескриптор процесса;
2) дескрипторы событий;
3) дескрипторы выходов;
4) сегменты кода, преобразующего значения переменных;
5) код операторов планирования событий.
В описании моделей реализованы: <оператор задания структуры> и
<оператор задания процесса>. Описание модели ВС задается алгоритмом ее
построения. Текст алгоритма помещается между ключевыми словами model
и endmod. Описание модели является отдельно транслируемой единицей,
также как описание структуры и описание процессов. Пример описания
модели представлен в таблице 4:
Таблица 4
model Computer def
structure(Computer) = CommonBase;
process(Computer.Microprocessor) = Xeon
process(Computer.Memory) = Ram64
endmod
Описание грамматики входного языка
Грамматика
языка
моделирования
μ
определяется
на
основе
расширенной формы Бэкуса-Наура (РБНФ) — наиболее распространенного
способа задания синтаксиса современных языков программирования, в
которой одни синтаксические категории последовательно определяются
через другие.
Описание грамматики в РБНФ представляет собой набор правил,
определяющих отношения между терминальными символами (терминалами)
и нетерминальными символами (нетерминалами).
Терминальные символы — это минимальные элементы грамматики, не
имеющие собственной грамматической структуры. В РБНФ терминальные
символы
—
это
либо
предопределённые
идентификаторы
(имена,
считающиеся заданными для данного описания грамматики), либо цепочки
— последовательности символов в кавычках или апострофах.
Нетерминальные символы — это элементы грамматики, имеющие
собственные имена и структуру. Каждый нетерминальный символ состоит из
одного или более терминальных и/или нетерминальных символов, сочетание
которых
определяется
правилами
грамматики.
В
РБНФ
каждый
нетерминальный символ имеет имя, которое представляет собой строку
символов.
Пример
описания
элемента
языка
моделирования
«структура»
(structure) в РБНФ:
описание структуры ::= structure идентификатор [параметры]
[интерфейс] def тело описания структуры endstr
Нетрудно заметить, что терминалами являются ключевые слова:
structure, def, endstr. Нетерминалы: идентификатор, параметры, интерфейс,
тело описания структуры. Данные элементы нуждаются в дополнительном
описании, которое подробно представлено в приложении 2.
Общая архитектура jit-компилятора
Компиляторы
составляют
существенную
часть
программного
обеспечения ЭВМ. Это связано с тем, что языки высокого уровня стали
основным средством разработки программ. Только очень незначительная
часть программного обеспечения, требующая особой эффективности,
программируется
с
помощью
ассемблеров.
В
настоящее
время
распространено довольно много языков программирования, а также большое
распространение получили языки, связанные с узкими предметными
областями, такие, как входные языки пакетов прикладных программ. Что
соответствует проблематике данной работы. Компилятор работает пофазно,
причём в процессе каждой фазы происходит преобразование исходной
программы из одного представления в другое.
Архитектура реализуемого компилятора состоит из классических [4, 5]
частей:
лексического,
синтаксического,
семантического
анализаторов,
объединенных в подсистему анализа, и генератора кода. Лексический
анализатор
формирует
синтаксическому
список
анализатору,
токенов, который
который
сгенерированную таблицу токенов,
в
свою
подаётся
очередь
на
вход
получает
проверяет значения токенов на
соответствие грамматическим правилам и формирует дерево разбора. Это
дерево передаётся на семантический анализатор, который формирует список
идентификаторов и проверяет их семантику, и в случае правильного
построения
генератор
кода
формирует
команды.
Полученный
сгенерированный код сохраняется в оперативной памяти компьютера для
последующего исполнения в симуляторе. Схема архитектуры приведена на
рисунке 11.
Исходная программа на языке μ
Лексический анализатор
Поток токенов
База данных
трансляции
Обработчик
ошибок
Синтаксический анализатор
Синтаксическое дерево
Семантический анализатор
Синтаксическое дерево разбора
и идентификаторы
Генерация кода
Блок данных и блок инструкций на машинном языке
Рисунок 11
Подсистема анализа
Лексический анализатор
На фазе лексического анализа входная программа, представляющая
собой поток литер, разбивается на лексемы – слова в соответствии с
определениями языка.
Основными формализмами, лежащим в основе реализации лексических
анализаторов, являются конечные автоматы и регулярные выражения.
Лексический анализатор может работать в двух основных режимах: либо как
подпрограмма, вызываемая синтаксическим анализатором для получения
очередной лексемы, либо как полный проход, результатом которого является
файл лексем. В данной работе используется первый режим, т.е. реализован
класс лексического анализатора на языке программирования C++ (методы
класса представлены в приложении 3).
Лексема (логическая единица языка) – это структурная единица языка,
которая состоит из элементарных символов языка и не содержит в своем
составе
других
структурных
единиц
языка.
Лексемами
языков
программирования являются идентификаторы, константы, ключевые слова
языка, знаки операций и т.п.
Поскольку лексический анализатор представляет собой первую фазу
компилятора, его основная задача состоит в чтении входных символов
исходной
программы,
их
группировании
в
лексемы
и
выводе
последовательностей токенов для всех лексем исходной программы. Поток
токенов пересылается синтаксическому анализатору для разбора.
Токен представляет собой структуру данных со следующими полями:
1. имя токена (значение) – отдельное слово, формирующееся из входных
символов исходного текста анализируемой программы.
2. тип токена – числовой идентификатор шаблона, под который подходит
имя токена.
3. позиция токена – позиция первого символа имени – номер строки и
позиция символа в строке. Это поле необходимо для определения
месторасположения ошибки в программе, если таковая имеется.
Реализация
структуры
токенов на языке
программирования
C++
представлена в таблице 5:
Таблица 5
struct Token {
string lexeme;
unsigned int id;
};
struct Position
{
unsigned int line;
};
struct TokenPosition {
Token token;
Position position;
};
Для построения лексического анализатора в данной работе был выбран
метод конечных автоматов.
Недетерминированный конечный автомат (НКА) – это пятерка
M = (Q, T, D, q0, F), где
Q – конечное множество состояний;
T – конечное множество допустимых входных символов (входной
алфавит);
D – функция переходов (отображающая множество Q(T{e}) во
множество
подмножеств
множества
Q),
определяющая
поведение
управляющего устройства;
q0 – начальное состояние управляющего устройства;
F – множество заключительных состояний.
Работа
конечного
автомата
представляет
собой
некоторую
последовательность шагов, или тактов. Такт определяется текущим
состоянием управляющего устройства и входным символом, обозреваемым в
данный момент входной головкой. Сам шаг состоит из изменения состояния
и, возможно, сдвига входной головки на одну ячейку вправо (рис. 13).
Рисунок 12
С теоретической точки зрения лексический анализатор не является
обязательной, необходимой частью компилятора. Его функции могут
выполняться на этапе синтаксического разбора. Однако существует
несколько причин, исходя из которых в состав реализуемого компилятора
включен лексический анализ:
– упрощается работа с текстом исходной программы на этапе
синтаксического разбора и сокращается объем обрабатываемой информации,
так как лексический анализатор структурирует поступающий на вход
исходный текст программы и выкидывает всю незначащую информацию;
– для выделения в тексте и разбора лексем возможно применять
простую, эффективную и теоретически хорошо проработанную технику
анализа, в то время как на этапе синтаксического анализа конструкций
исходного языка используются достаточно сложные алгоритмы разбора;
– сканер отделяет сложный по конструкции синтаксический анализатор
от работы непосредственно с текстом исходный программы, структура
которого может варьироваться в зависимости от версии входного языка – при
такой конструкции компилятора при возможном переходе от одной версии
языка к другой достаточно только перестроить относительно простой сканер.
Функции, выполняемые лексическим анализатором, и состав лексем,
которые он выделяет в тексте исходной программы, могут меняться в
зависимости от версии компилятора. В основном лексические анализаторы
выполняют исключение из текста исходной программы комментариев и
незначащих пробелов, а также выделение лексем следующих типов:
идентификаторов, строковых, символьных и числовых констант, ключевых
(служебных) слов входного языка.
Программа поочерёдно считывает из анализируемого текстового файла
символы и записывает их в массив. Если встретился символ, определяющий
один из операторов языка μ, либо символ пробела, либо символ перевода
строки, то программа проверяет полученное слово в массив на соответствие
одному из шаблонов:
 ключевое слово
 имя типа
 оператор
 имя переменной
 константа
Для проверки слова на соответствие шаблону существуют таблицы
ключевых слов, операторов и типов. Полная таблица соответствий лексем и
соответствующих кодов представлена в приложении 4. Если слово не
эквивалентно ни одному из слов в данных таблицах, то программа проверяет,
является ли оно словом (именем переменной) или числом (константой). Если
слово подходит под один из шаблонов, то создаётся токен, являющийся
одним из элементов динамического списка. Ему присваивается имя, тип и
номер строки, в которой встретился первый символ слова в текстовом файле.
Далее программа распознаёт следующее слово, создаёт новый токен и
присоединяет его к предыдущему. В случае успешного анализа и разбора
всех символов файла на отдельные слова, функция-анализатор возвращает
указатель на первый элемент списка токенов. Если одно из слов не удаётся
разобрать, т.е. оно не подходит ни под один шаблон, выводится сообщение
об ошибке, а функция-анализатор возвращает ноль.
Рассмотрим
пример построения списка токенов для следующей
строки:
int a1; a1 = 5*b;
Анализатор считывает символы слева направо, заполняя ими массив,
пока не встретит пробел или символ-оператор. В данном примере, встретив
первый пробел, в буфере анализатора будет находиться слово «int».
Программа находит это слово по таблице типов, создаёт элемент типа
«токен», присваивает ему значение «int» и числовое значение типа «102», а
также номер строки, в которой встретился первый символ 'i'. Полученный
элемент присоединяется к хвостовому элементу списка токенов. Далее
массив очищается, и программа анализирует следующие символы. Таким
образом, мы получим список токенов со значениями, представленный на
рисунке 12:
Рисунок 13
Результат работы лексического анализатора сохраняется в структуре
TokenPosition, описанной выше, и передается на вход синтаксическому
анализатору для последующих действий. Кроме того, на этапе лексического
анализа собирается таблица идентификаторов, которая также передается
синтаксическому анализатору.
Синтаксический анализатор
Синтаксический анализатор (синтаксический разбор) – это часть
компилятора, которая отвечает за выявление основных синтаксических
конструкций входного языка. В задачу синтаксического анализа входит:
найти и выделить основные синтаксические конструкции в тексте входной
программы,
установить
тип
и
проверить
правильность
каждой
синтаксической конструкции и, наконец, представить синтаксические
конструкции
в
виде,
удобном
для
дальнейшей
генерации
текста
результирующей программы.
В основе синтаксического анализатора лежит распознаватель текста
входной программы на основе грамматики входного языка. Распознаватель
дает ответ на вопрос о том, принадлежит или нет цепочка входных символов
заданному языку. Однако, как и в случае лексического анализа, задача
синтаксического
разбора
не
ограничивается
только
проверкой
принадлежности цепочки заданному языку. Синтаксический разбор – это
основная
часть
компилятора
на
этапе
анализа.
Без
выполнения
синтаксического разбора работа компилятора бессмысленна. Все задачи по
проверке синтаксиса входного языка могут быть решены на этапе
синтаксического разбора.
Выходом лексического анализатора является таблица лексем (или
цепочка лексем). Эта таблица образует вход синтаксического анализатора,
который исследует только один компонент каждой лексемы – ее тип.
Остальная информация о лексемах используется на более поздних фазах
компиляции при семантическом анализе, подготовке к генерации и
генерации кода результирующей программы. Синтаксический анализ (или
разбор) – это процесс, в котором исследуется таблица лексем и
устанавливается, удовлетворяет ли она структурным условиям, явно
сформулированным в определении синтаксиса языка.
В процессе разработки синтаксического анализатора были рассмотрены
и изучены основные типы алгоритмов реализации парсера:
1. Нисходящий синтаксический анализ - продукции грамматики
раскрываются, начиная со стартового символа, до получения
требуемой последовательности токенов.
2. Восходящий
синтаксический
анализ
-
продукции
восстанавливаются из правых частей, начиная с токенов и кончая
стартовым символом.
3. В подсистеме анализа для синтаксического разбора используется
один из методов LL(k) анализа – метод рекурсивного спуска. В
таком методе
В результате был выбран и применен алгоритм нисходящего разбора,
т.к. называемый метод рекурсивного спуска, основанный на использовании
парсящих процедур, соответствующих правилам контекстно-свободной
грамматики в форме РБНФ, взаимно вызывают друга. Правила применяются
последовательно.
Токены,
полученные
от
лексического
анализатора,
поглощаются слева-направо. Цель использования такого анализатора
заключается в максимально быстрой обработке входного потока лексем и
сопоставлении его с описанием языка заданного в определенном формате.
Основные парадигмы метода рекурсивного спуска:
– каждому нетерминалу соответствует отдельная процедура (функция),
распознающая (выбирающая и «закрывающая») одну из правых частей
правила, имеющего в левой части этот нетерминал (т.е. для каждой группы
правил пишется свой распознаватель);
– во входной строке имеется указатель (индекс) на текущий
«закрываемый символ». Этот символ и является основанием для выбора
необходимой правой части правила. Правила выбора базируются на
построении множеств выбирающих символах, как это принято в LL(1) –
грамматике;
– просмотр выбранной части реализован в тексте процедурыраспознавателя путем сравнения ожидаемого символа правой части и
текущего символа входной строки;
– если в правой части ожидается терминальный символ и он совпадает
с очередным символом входной строки, то символ во входной строке
пропускается, а распознаватель переходит к следующему символу правой
части;
– несовпадение терминального символа правой части и очередного
символа входной строки свидетельствует о синтаксической ошибке;
– если в правой части встречается нетерминальный символ, то для него
необходимо вызвать аналогичную распознающую процедуру (функцию).
Ниже приведен листинг одной из главных процедур синтаксического
анализатора, которая определяет, на какие именно рекурсивные процедурыпроверки будет отправлен токен для сравнения его с грамматикой входного
языка.
void SyntaxAnalyser::isDefineOfStructure(vector<TokenPosition>::iterator i){
isMatch(200, "structure");
i++;
isIdentifier(i);
isParamsDefinitions(i);
isInterfaceDefinitions(i);
isMatch(201, "def");
isBodyOfStructureDescription(i);
isMatch(202, "endstr");
}
Таким образом, синтаксический анализатор представляет собой
множество взаимосвязанных функций, каждая из которых соответствует
определённому грамматическому правилу описания процессов на языке
моделирования μ. Каждая функция на вход принимает указатель на
стартовый токен, с которого нужно начать анализ. В случае соответствия
значений токенов синтаксическому правилу функция строит узел дерева.
Если нужно, рекурсивно вызывает другие функции для построения дочерних
узлов дерева. На выход функции передаётся указатель на токен, на котором
закончился анализ данной функции. Этот токен будет являться стартовым в
следующих функциях. В случае успешного анализа функция возвращает узел
синтаксического дерева, иначе выводится сообщение об ошибке с указанием
строки в текстовом файле, а также позиции слова, которое является причиной
ошибки.
Для каждого из правил грамматики определена соответствующая
функция, которая проверяет значения входного списка токенов на
соответствие правилу и возвращает лист дерева. Такие функции будем
называть функциями парсинга. Эти функции могут вызывать другие функции
парсинга, формируя новые узлы синтаксического дерева. Самые вложенные
функции обычно формируют листья дерева. Кроме того, вложенные функции
сдвигают указатель на токен, присваивая ему токен, находящийся за
последним проанализируемым токеном во вложенной функции. Например,
если на вход вложенной функции передан токен со значением «if», то после
выполнения этой функции указатель переместится на следующий за «endi»
токен.
Структура узла синтаксического дерева показана на рисунке 3.
Рисунок 14
Тип узла определяется функцией парсинга. Он указывает, по какому
синтаксическому правилу была разобрана часть строки, соответствующая
этому узлу. Имя (значение) обычно эквивалентно текстовому значению
соответствующего этому узлу токена. Узел также имеет указатель на
родителя и указатель на дочерний узел. Если дочерних узлов несколько, то
они формируются в виде списка, где каждый предыдущий узел связывается
со следующим, и все эти узлы указывают на одного и того же родителя.
Рассмотрим
непосредственно
алгоритм
анализа
токенов
и
формирования из них синтаксического дерева на примере описания процесса.
Начальная функция, в качестве входного параметра которой является
указатель на первый токен, определяет синтаксическое правило:
описание процесса ::= process идентификатор [параметры]
[интерфейс] тело описания процесса endpro
Функция
создаёт
корневой
узел
и
присоединяет
к
нему
дополнительный узел с типом NodeProcess. Первый анализируемый токен
должен иметь текстовое значение «process». Второй токен должен
определять название процесса. Названия всех переменных, процессов
должны
подходить
под
правило
идентификатор.
Далее
следуют
необязательные элементы – список параметров и интерфейс. Функция
анализирует токены на наличие парных скобок, и если скобки присутствуют,
то она передаёт указатель на следующий за скобкой токен в функцию,
соответствующую правилу параметры. Такая функция возвращает узел
дерева типа NodeDefine с листьями, являющимися названиями входных
переменных, либо входов и выходов. Формируется узел, являющийся
родителем для узла с типом NodeDefine и потомком для узла NodeRoutine.
Новый узел имеет тип NodeParam или NodeInterface, в зависимости от того,
являются ли значения токенов между скобками списком параметров или
интерфейсом. Проверяется наличие закрывающейся скобки.
Далее функция проверяет значение следующего токена. Если оно
эквивалентно слову «initial», то этот токен передаётся в функцию,
соответствующию синтаксическому правилу инициализация Полученный
узел связывается с узлом типа NodeProcess.
Далее в цикле проверяется значение токена на соответствие ключевому
слову «event». С каждой итерацией вызывается функция с правилом
описание события и возвращаемый узел присоединяется к родительскому
узлу NodeProcess.
Правила инициализация и описание события соответствуют одной и
той же функции, отличается тип возвращаемого токена (NodeInitial или
NodeEvent соответственно). Кроме того, возвращаемый узел не имеет имени,
если он сформирован по правилу инициализация а в правиле описание
события не должно быть блока определения переменных.
Функция, соответствующая блоку инициализации или блоку события,
обходит каждый токен, начиная с токена, находящегося после входного со
значением «initial» или «event». Она передаёт каждый токен в следующую
функцию с правилом «Оператор». Эта функция возвращает новый узел
дерева, который цепляется к родительскому узлу с типом NodeInitial или
NodeEvent.
Функция с правилом оператор не создаёт узлов дерева, но передаёт
входной токен следующим функциям в зависимости от значения этого
токена. Такие функции возвращают узлы дерева, которые и являются
возвращаемым значением функции с правилом оператор.
Одним из операторов является конструкция «if». Формируемое по
такому правилу поддерево имеет корень с типом NodeIf и дочерние узлы:
узел
с
проверяемым
выражением,
который
строится
по
правилу
«Выражение»; узел с типом NodeThen, являющийся родителем для узловвыражений, которые могут быть в теле оператора «if». Если у оператора есть
блок «else», то программа формирует узел с типом NodeElse, к которому
аналогично крепятся узлы-выражения.
Для построения узлов-выражений используется рекурсивная функция
со следующим алгоритмом. Находится последний токен, который являются
концом выражения (определяются по токену-разделителю со значением
конца блока или со знаком-разделителем «;»). Если есть внешние парные
скобки, то они отбрасываются (то есть, указатель на первый токен сдвигается
на один токен вперёд, указатель на последний – на один назад). Затем
находится наименее приоритетный токен-оператор (табл. 6), который и будет
узлом дерева. Список токенов разбивается на два списка. Первый список
указывает на входной токен и заканчивается токеном, находящимся перед
оператором. Начало второго списка является указателем на токен за
оператором. Каждый список подаётся в рекурсивную функцию, которая
формирует левый и правый потомки для созданного узла-оператора. Если в
выражении не осталось операторов, то происходит выход из рекурсивной
функции, которая возвращает лист дерева. В результате разбора выражения
мы получим либо единственную вершину с типом «переменная» или
«константа», либо поддерево с листьями аналогичных типов и узлами типа
«оператор».
Таблица 6
Знак оператора
*
**
/
+
<
>
<=
>=
==
=/=
&
|
=
Значение
умножение
пересечение графов
деление
сложение
разность
меньше
больше
меньше или равно
больше или равно
равно
не равно
логическое «и»
логическое «или»
присвоение
Приоритет
7
7
7
6
6
5
5
5
5
4
4
3
2
1
Функция для правила оператор планирования события формирует
корень поддерева с типом NodeSchedule, с которым связывается лист с
именем события и узел, описывающий выражение.
Функция для правила оператор передачи сообщения создаёт корень
поддерева, вызывает функцию для разбора выражения, которая возвращает
дочерний узел. Если один из анализируемых токенов имеет значение «via»,
то функция создаёт узел с аналогичным названием и добавляет дочерние
узлы с названием выходов, эквивалентным именам встречаемых токенов.
В результате успешного анализа и разбора мы получим дерево разбора,
где узлами являются знаки операций или ключевые слова, а листьями –
имена переменных или константы.
Пример на рисунке 16 демонстрирует построенное синтаксическое
дерево для следующей конструкции:
process Regular
initial let R be in 0.0 endin
event R; let R be in 2.4 ende
endpro
Рисунок 15
Для
проверки
правильности
построения
дерева
используется
рекурсивная функция, которая распечатывает его. Она получает указатель на
корень дерева и начинает обход: либо от узла к дочерним элементам, либо от
первого дочернего к узлу, и затем ко второму дочернему элементу. Порядок
обхода зависит от типа элемента дерева. Результатом работы функции
является вывод на экран строки символов, соответствующей строке, которая
была проанализирована.
Для полного представления о типе и структуре найденной и
разобранной синтаксической конструкции входного языка в принципе
достаточно
знать
последовательность
номеров
правил
грамматики,
примененных для ее построения. Структура синтаксического дерева отражает
синтаксис
языка
программирования,
на
котором
написана
исходная
программа.
В результате работы синтаксического анализатора строится атрибутное
дерево разбора. Далее включаются в работу семантический анализ и
генератор кода.
Семантический анализатор
Семантические ошибки в основном относятся к:
1) неправильным и/или несовместимым типам
переменных;
2) неправильным типам аргументов функций и
операторов;
3) несуществующим переменным, меткам, функциям;
4) несуществующим полям объектов, функций;
5) неправильно сформированным строкам;
6) неправильному формированию списка аргументов для функций;
В данной работе семантический анализ реализован дополнительными
функциями синтаксического анализа, которые проверяют выше описанные
семантические ошибки. Семантический анализатор взаимодействует с
семантическим посредством специально реализованных функций. Например:
//проверяем соответствует ли полученная лексемма ожидаемой лексемме
bool SyntaxAnalyser::isMatch(int n, string s){
if (n!=i->token.id) {
cout << "Wait's the '" << s <<"' , but get - " << i->token.lexeme << "
Number of string= ";
printf("% i\n", i->position);
return(false);
}
else return (true);
i++;
}
При разработке подсистемы анализа, были использованы основные
парадигмы объектно-ориентированного программирования. В результате
реализации были написаны классы лексического и синтаксического
анализаторов. Синтаксический анализатор активно использует объекты и
методы лексического анализатора.
Подсистема синтеза
Генерация кода
В результате работы синтаксического анализатора строится дерево
разбора. Далее включаются в работу семантический анализатор и генератор
кода. В качестве входного параметра указывается корень синтаксического
дерева.
Выходной
параметр
–
указатель
на
структуру
кода.
Инициализируются список идентификаторов и список команд. Происходит
разбор синтаксического дерева в глубину.
Эти списки содержат в качестве элементов дескрипторы различных
типов, связанные между собой ссылками.
Аналогично в результате трансляции описания структуры создается
машинный код (команды и структуры данных), который можно считать
объектным кодом некоторой функции (процедуры), возвращающей после
вызова в качестве значения ссылку на структуру. Внутри этого кода, в свою
очередь, находятся вызовы других функций. Эти функции создают в памяти
внутреннее представление структуры, состоящее из взаимосвязанных
дескрипторов. Для описанного примера создаются:
 дескриптор графа, содержащий уникальный идентификатор UId, имя
графа, тип, количество вершин и дуг, а также ссылки на списки вершин
и дуг; имя может быть более сложным, чем просто идентификатор,
поэтому предусмотрена возможность разделения имени на заглавную
часть и продолжение; кроме этого, граф может быть не обыкновенным,
бинарным графом, а гиперграфом, и сведения об этом также
представлены в дескрипторе;
 дескрипторы вершин; вершины графа (элементы Node) – узлы сети –
описываются каждая собственным дескриптором; связи между узлами
сети могут в зависимости от задачи исследования рассматриваться как
дуги или ребра графа, но могут рассматриваться и как вершины
(например, линия связи может представляться как самостоятельная
сущность);
 дескрипторы ребер (дуг) графа;
 дескрипторы входов и выходов;
 вспомогательные дескрипторы, обеспечивающие гибкость выполнения
операций над структурами.
Графы описывают статику моделируемой вычислительной системы,
т.е. те свойства, которые не зависят от времени. Но в системе постоянно
происходят изменения, т.е. система динамична. Для описания динамики
используются процессы.
С каждой вершиной графа (структуры) модели может быть связан
процесс. Процессы протекают во времени. События «происходят» в
некоторые моменты времени.
В результате трансляции описания процесса создается машинный код
(команды и структуры данных), который можно считать объектным кодом
некоторой функции (процедуры), возвращающей после вызова в качестве
значения ссылку на процесс. Внутри этого кода, в свою очередь, находятся
вызовы других функций. Эта функция создает в памяти внутреннее
представление процесса, состоящее из взаимосвязанных дескрипторов и
сегментов кода. Создаются:
 дескриптор процесса, содержащий уникальный идентификатор, имя и,
возможно, его продолжение, количество событий, относящихся к
процессу, и их список, а также количества входов/выходов и
соответствующие
списки;
состояния
процесса
изображаются
значениями переменных, поэтому ссылка на список переменных также
содержится в дескрипторе;
 дескрипторы отдельных событий;
 дескрипторы выходов процесса;
 сегменты кода, преобразующего значения переменных состояния
процесса;
 код операторов планирования будущих событий.
Генератор кода получает на вход синтаксическое дерево, проверяет
семантику, находит соответствие между элементами дерева и доступными
командами и генерирует код.
Код представляет собой два блока:
 блок данных
 блок команд
Для блока данных выделяется память, равная сумме значений, указанных
в поле идентификатора «размер данных». Если идентификатор имеет тип
«константа», то его значение записывается в блок данных. Для блока команд
выделяется память, необходимая для хранения всех сгенерированных
команд, и выполняется присвоение из списка команд.
Для того, чтобы вычислить размер для каждого типа блоков,
необходимо построить список идентификаторов и список команд.
Идентификатор – это структура, хранящая имя переменной или
структуры, данные, тип и размер данных (в байтах), а также смещение
относительно первого элемента в блоке данных (в байтах). При генерации
блока данных из идентификатора считывается только размер данных, и если
идентификатор является константным значением, то в блок данных
записывается значение идентификатора.
Команда
–
это
структура,
которая
содержит
идентификатор
вызываемой библиотечной функции, количество аргументов, указатель на
индексы аргументов, и расположение в блоке данных, куда будет записан
возвращаемый результат. Сгенерированная команда записывается в список
команд.
Функция генерации кода инициализирует список идентификаторов и
список команд. Далее она проверяет, соответствует ли входной узел дерева
указанному типу и создаёт идентификатор с таким типом. Т.к. эта структура
является динамической, то размер идентификатора равен размеру указателя
на область памяти. Смещение идентификатора равно суммарному размеру
всех предыдущих идентификаторов. Затем создаётся структура команды, ей
присваивается идентификатор библиотечной функции, которая отвечает за
создание процесса в памяти. У этой функции нет аргументов, но есть
возвращаемой
значение,
поэтому
команде
устанавливаем
значение
аргументов равным нулю, а расположение возвращаемого результата будет
равно смещению созданного идентификатора.
Например, если процесс имеет имя, необходимо создать новый
идентификатор типа «строка» с размером, равным длине строки. Затем
создаётся инструкция с идентификатором функции «установить имя
процесса» без возвращаемого значения и с одним аргументом, индекс
которого равен смещению созданного ранее идентификатора.
Далее идёт генерация кода для создания входов, выходов и событий
процесса. Так как события могут иметь операторы планирования событий,
которых ещё нет в списке идентификаторов, то сначала создадим
идентификатор для каждого события.
Если в структуре синтаксического дерева есть узел с определением
интерфейса процесса, то создадим соответствующие идентификаторы с
именами элементов интерфейса. Далее добавим в список инструкций
команды с идентификаторами функций «создание входа» или «создание
выхода» и команды присвоения имени входам или выходам.
Затем
строятся
команды
для
создания
событий.
Так
как
идентификаторы событий уже создавались, то находим их по имени узла
дерева, соответствующего типу «событие». В команде создания события в
качестве расположения возвращаемого значения присваивается смещение
найденных идентификаторов. Если событие имеет имя, то создаём
идентификатор строки и передаём его смещение в качестве индекса входного
аргумента в команду присвоения имени.
Блок инициализации обрабатывается так же, как и блок события, но он
не имеет имени и в нём могут быть определены переменные. Переменные
определяются так же, как и входы и выходы, но имеют соответствующий тип
и размер. Следует отметить, что если имя новой переменной совпадает с
именем уже существующей переменной или с именами других структур
процесса, то выводится сообщение о семантической ошибке.
Блок оператора планировщика события создаёт идентификатор и
команду создания планировщика, затем создаёт дополнительную команду,
которая связывает планировщик с запланированным событием. Также
строятся команды для выражения, которое задаёт планировщику время, через
которое тот должен запустить событие.
Блок оператора передачи сообщения генерирует код сообщения и
создаёт идентификатор, куда будет записываться конечный результат. Затем
для каждого выхода, описанного в блоке оператора, создаётся идентификатор
и
команда
создания
идентификатором.
Для
структуры
Out,
примера
которая
можно
связывается
создать
с
этим
дополнительную
инструкцию, которая выводит конечный результат оператора на экран.
Для
построения
используется
списка
рекурсивная
команд
функция,
арифметических
которая
выражений
анализирует
часть
синтаксического дерева в глубину, строит команды сначала для нижних
уровней дерева, затем для верхних, возвращая на предыдущий уровень
рекурсии вспомогательные идентификаторы. Здесь также проверяется
семантика выражений, и если обнаружена семантическая ошибка (например,
присвоение константе или недопустимая операция над разными типами), то
выводится сообщение об ошибке.
В конце работы генератора кода в список команд добавляется
инструкция, которая устанавливает возвращаемым значением позицию в
блоке данных, указывающую на структуру процесса. Последним шагом
будет формирование и вывод в качестве возвращаемого значения блока
данных и блока команд.
Внутреннее представление объектов моделирования
Описание модели компьютерной сети на языке моделирования задается
алгоритмом ее построения. Текст алгоритма помещается между ключевыми
словами
model
и
endmod.
Описание
модели
является
отдельно
транслируемой единицей, так же как и описание структуры, и описание
процессов.
Пример описания модели:
model ComputerNetwork def
structure(ComputerNetwork) = TokenRing(10);
process(ComputerNetwork.Node) = Computer;
process(ComputerNetwork.Channel) = Fibre;
endmod
В результате трансляции описания модели создается машинный код
(команды и структуры данных), который можно считать объектным кодом
некоторой функции (процедуры), возвращающей после вызова в качестве
значения ссылку на модель.
Итоговое представление модели имеет вид сложной списковой
структуры, развивающей представления графов, описанные в [6], и
включающие фрагменты кода.
Существует несколько способов хранения графовых структур в
памяти компьютера:
1. матрица смежности;
2. матрица инциденции;
3. массив дуг;
4. списки смежности.
В настоящей работе использовано списковое представление графов,
поэтому рассмотрим его подробнее.
Связный список – структура, элементы которой имеют один и тот же
формат и связаны друг с другом с помощью указателей, хранящихся в самих
элементах. В односвязных списках каждый элемент ссылается только на
следующий элемент списка.
В линейном двусвязном списке продвижение возможно в любом из
двух направлений по цепочке элементов. Каждый элемент двусвязного
списка содержит два указателя: указатель на следующий элемент, или
прямой указатель, и указатель на предыдущий элемент, или обратный
указатель (рис.17). У первого элемента двусвязного списка обратный
указатель пустой, а у последнего элемента двусвязного списка – прямой
указатель.
Рисунок 16
В данной работе все элементы графа организованы в двусвязные
списки. Вся основная информация о графе содержится в структуре,
называемой дескриптором графа (рис. 18).
1
2
3
4
5
6
7
8
9
10
11
12
Рисунок 17
Пояснения к структуре, описывающей граф: 1) равенство признака 4
говорит о том, что это дескриптор графа; 2,3) указатели на предыдущий и
следующий дескрипторы графов (все графы объединены в двунаправленный
список); 4) числовой идентификатор графа, генерируемый системой
автоматически; 5,6)имя в виде символьного массива и его продолжение; 7)
тип графа; 8) арность равна 2, т.к. дуги обыкновенные, а граф не является
гиперграфом (обобщённый вид графа, в котором каждым ребром могут
соединяться не только две вершины, но и любые подмножества вершин); 10)
указатель на начало списка вершин; 12) указатель на начало списка дуг.
Информация о каждой вершине содержится в структуре, называемой
дескриптором вершины ( рис.19).
1
2
3
4
5
6
7
8
9
11
11
12
13
Рисунок 18
Пояснения к структуре, описывающей вершину графа: 1) равенство
признака 5 говорит о том, что это дескриптор вершины; 2,3) указатели на
дескрипторы предыдущей и следующей вершины графов; 4) числовой
идентификатор вершины, генерируемый системой автоматически; 5,6)имя в
виде символьного массива и его продолжение; 7) тип вершины; 8) ссылка на
дескриптор графа, к которому принадлежит вершина; 10) указатель на
первый из списка дескрипторов входов; 12) указатель на первый из списка
дескрипторов выходов; 13) связь с процедурой, проводящей операции над
данным графом.
Дескрипторы вершин организованы в двухсвязный список. Каждой
вершине сопоставляются входы и выходы. На рисунке 20 у вершины А
имеется 2 входа и 3 выхода . Вход M, выходы Q и R имеют степень 1 ( число
входящих и, соответственно выходящих дуг равно 1). Выход L имеет степень
2, а вход P – степень 3.
Рисунок 19
В данной работе каждая вершина может иметь по несколько входов и
выходов. Информация о входах и выходах содержится в структурах,
называемых дескрипторами (рис. 21).
Они, так же, как и вершины,
организованы в двусвязные списки.
1
2
3
4
5
6
7
8
9
10
11
Рисунок 20
Пояснения к дескриптору, описывающему вход: 1) равенство признака
6 говорит о том, что это дескриптор входа (для выхода структура аналогична,
но признак =7); 2,3) указатели на дескрипторы предыдущего и следующего
входа (выхода); 4) числовой идентификатор входа (выхода), генерируемый
системой автоматически; 5,6)имя в виде символьного массива и его
продолжение; 7) тип входа (выхода); 8) количество инцидентных дуг; 9)
указатель на первый элемент в списке дескрипторов дуг, соответствующих
данному входу (выходу); 10) связь с процедурой, проводящей операции над
данным графом; 11) указатель на дескриптор соответствующей вершины.
Дуги в данном представлении
связаны не непосредственно с
вершинами, а с их входами и выходами через вспомогательные элементы.
Дескриптор дуги изображен на рисунке 22:
1
2
3
4
5
6
7
8
9
10
11
12
Рисунок 21
Пояснения к дескриптору, описывающему дугу: 1) признак 8 говорит о
том, что данная структура является дескриптором дуги; 2,3) указатели на
дескрипторы предыдущей и следующей дуги; 4) числовой идентификатор
дуги, генерируемый системой автоматически; 5,6) в поле имени записывается
имя соответствующего выхода, а в продолжение имени – входа; 7) тип дуги;
8) арность = 2, т.к. дуга не является в данной работе гипердугой гиперграфа;
9,11) поэтому количество соответствующих данной дуге входов и выходов –
по одному; 10,12) указатели на соответствующие данной дуге дескрипторы
входа и выхода.
Вспомогательные элементы типа вход/выход->дуга организованы в
отдельные односвязные списки для каждого дескриптора входа или выхода
(рис.23, 24).
Рисунок 22
Рисунок 23
Вспомогательные элементы типа дуга->вход, дуга->выход связаны
все между собой в 2 односвязных списка (рис. 25).
Рисунок 24
Используемое представление графа позволяет не только по имени
графа получить список его вершин и дуг, но также по каждой дуге (зная
указатель на неё), получить информацию о её входе и выходе, а также о
вершинах, которые дуга соединяет. Таким образом, мы можем легко узнать,
как устройства моделируемой информационной системы подключены друг к
другу.
Заключение
В результате выполнения поставленного задания разработан jitкомпилятор языка моделирования μ, который производит лексический,
синтаксический и семантический анализ, генерирует идентификаторы и
команды
для
построения
машинного
эквивалента
описания
модели
компьютерной сети. При неправильном описании модели выводится список
ошибок, который позволяет быстро локализовать и устранить ошибки в
описании.
Компилятор
преобразует
входную
программу
на
языке
моделирования в машинный код в памяти целевой машины.
Создание jit-компилятора является достаточно трудоемким процессом.
Поэтому должна быть серьезная мотивация для его выполнения. В данном
случае
полученные
результаты
будут
использоваться
в
системе
моделирования информационных процессов μ.
Во время выполнения работы, было сделано две публикации:
1. Миков А.И., Храмцова В.В. Архитектура JIT-компилятора для
программной платформы моделирования информационных процессов.
// Технологии разработки информационных систем ТРИС-2013. – 2013.
– Материалы конференции, том 1. – С. 77–82.
2. Миков А.И., Храмцова В.В. Архитектура и реализация транслятора
описаний структур компьютерных сетей. // Информатизация и связь. –
2013. – № 5. – С. 74–76.
Список использованных источников
1. Mikov A.I. Performance evaluation. / A.I. Mikov. – Krasnodar: Kuban state
university, 2013. – 100 с.
2. Миков А.И., Мезенцева А.С. Характеристики геометрических графов,
моделирующих ad hoc сети. // Информатизация и связь. – 2012. – № 5. – С.
85–88.
3. Миков А.И. Связность автономных беспроводных компьютерных сетей в
местностях с плохой инфраструктурой. // Экологический вестник научных
центров Черноморского экономического сотрудничества. – 2013. – № 4.
4. Вирт Н. Построение компиляторов./ Н. Вирт. – М.: ДМК Пресс, 2010. –
192 с.
5. Ахо А., Лам М., Сети Р., Ульман Д. Компиляторы. Принципы,
технологии, инструменты./ А. Ахо и др. – М.: Вильямс, 2002. – 768 c.
6. Королев Л.Н., Миков А.И. Информатика. Введение в компьютерные
науки: Учебник/ Л.Н. Королев, А.И. Миков. – М.: Абрис, 2012. – 367 с.
7. Замятина Е.Б., Миков А.И. Программные средства системы имитации
Triad.NET для обеспечения ее адаптируемости и открытости. //
Информатизация и связь. – 2012. – № 5. – С. 130–133.
8. Миков А.И., Моспан Н.В. Параллельная обработка паттернов в больших
данных имитационного моделирования. // Информатизация и связь. –
2013. – № 2. – С. 141–143.
9. Миков А.И. Информационные процессы и нормативные системы в IT:
Математические модели. Проблемы проектирования. Новые подходы. –
М.: Книжный дом «ЛИБРОКОМ», 2013.
10.Грис Д. Конструирование компиляторов для цифровых вычислительных
машин. – М.:Мир, 1975.
11.Хантер Р. Основные концепции компиляторов. – М.: Вильямс, 2005.
12.Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и
компиляции. Том 1.Синтаксический анализ. – М.: Мир, 1978.
13.Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и
компиляции. Том 2.Компиляция. – М.: Мир, 1978.
14. Залогова Л.А. Разработка Паскаль-компилятора. – М.: БИНОМЛаборатория знаний,2007.
15. Хоар Ч. Взаимодействующие последовательные процессы. – М.: Мир,
1989.
16. Компаниец Р., Маньков Е., Филатов Н. Системное программирование.
Основы построения трансляторов. – М.: КОРОНА принт, 2002.
17. Карпов Ю. Теория и технология программирования. Основы построения
трансляторов.– СПб.: BHV-СПб, 2003.
18. Питерсон Дж. Теория сетей Петри и моделирование систем. – М.: Мир,
1984.
19. Королев Л.Н., Миков А.И. Информатика. Введение в компьютерные
науки. – М.: Высшая школа, 2003 (гл. 3, 7).
20. Макаров А.В., Скоробогатов С.Ю., Чеповский А.М. Common
Intermediate Language и системное программирование в Microsoft .NET. –
М.: Интернет Университет
21. Рейуорд-Смит В.Дж. Теория формальных языков. Вводный курс. – М.:
Радио и связь, 1988.
22. Саломаа А. Жемчужины теории формальных языков. – М.: Мир, 1986.
23. Поляков К.Ю. Искусство программирования на языке Си. – СПб, 2009.с.48
24. Уинер Р. Язык ТУРБО СИ .- М.: Мир, 1991. – с.208,229,233
25. Замятина Е.Б., Миков А.И. Программные средства системы имитации
Triad.NET для обеспечения ее адаптируемости и открытости. //
Информатизация и связь. – 2012. – № 5. – С. 130–133.
26. Миков А.И., Моспан Н.В. Параллельная обработка паттернов в больших
данных имитационного моделирования. // Информатизация и связь. –
2013. – № 2. – С. 141–143.
27. Керниган Б., Ритчи Д. Язык программирования С, 2-е издание – М.:
Издательский дом "Вильямс", 2009. -304 с
28. Cooper K., Torczon L. Engineering a compiler. – Morgan Kaufman, 2
edition, 2011. 765 c
29. Хаггард Г., Шлипф Дж., Уайтсайдс С. Дискретная математика для
программистов.- М.: БИНОМ. Лабаратория знаний, 2010. - с.354
30. Новиков Ф.А. Дискретная математика для программистов .Учебник для
вузов. 3-е издание.- СПб: ПИТЕР, 2009. – с.250, 257
31. Хэзфилд
Р.,
Кирби
Л.
Искусство
программирования
на
С.
Фундаментальные алгоритмы, система данных и примеры приложений –
с.171
32. Юров В. И. Assembler: практикум. – СПб.: ПИТЕР, 2002.
33. Пятибратов А.П., Гудыно Л. П., Кириченко А.А. Вычислительные
системы, сети и телекоммуникации: учебник для студентов вузов. – М.:
ИНФРА-М: Финансы и статистика, 2008.
34. Таненбаум Э. Архитектура компьютера. – СПб.: Питер, 2002.
35. Миков А.И., Храмцова В.В. Архитектура JIT-компилятора для
программной платформы моделирования информационных процессов.
// Технологии разработки информационных систем ТРИС-2013. – 2013.
– Материалы конференции, том 1. – С. 77–82.
36. Миков А.И., Храмцова В.В. Архитектура и реализация транслятора
описаний структур компьютерных сетей. // Информатизация и связь. –
2013. – № 5. – С. 74–76.
37. В.А. Серебряков,
М.П. Галочкин
компиляторов. – Москва 2004 год.
Основы
конструирования
Приложения
Приложение 1
Примеры структур систем, сетей и их описания.
Топология «Кольцо» (рис. ):
Рисунок 25
Описание:
structure Ring def
Ring = cycle(4)(P[1:4]);
endstr
Топология «Решетка» (рис. ):
Рисунок 26
Описание:
structure Matrix def
Matrix = rectan(4,4)(P[1:4, 1:4](A, B, C, D));
endstr
Топология «Тор» (рис. ):
Рисунок 27
Описание:
structure Torus def
Torus = rectan(4,4)(P[1:4, 1:4](A, B, C, D));
for i=1 to 4 do
Torus = Torus + (P[1,i](B)<>P[4,i](D))
+(P[i,1](A)<>P[i,4](C))
endf
endstr
Приложение 2
Грамматика языка моделирования µ в БНФ
Описание структуры
описание структуры ::= structure идентификатор [параметры]
[интерфейс] def тело описания структуры endstr
параметры ::= (описание параметров одного типа {; описание параметров
одного типа})
описание параметров одного типа ::= имя типа
идентификатор{,идентификатор}
интерфейс ::= (элемент интерфейса {; элемент интерфейса})
элемент интерфейса ::= спецификатор полюса список имен полюсов
список имен полюсов ::= имя группы полюсов {,имя группы полюсов}
имя группы полюсов ::= идентификатор [ [диапазон индексов
{,диапазон индексов}] ]
диапазон индексов ::= целочисленное выражение : целочисленное
выражение | целочисленное выражение
имя ::= идентификатор [ [индекс {,индекс}] ] [ (идентификатор [
[индекс {,индекс}] ] ) ]
индекс ::= целочисленное выражение
спецификатор полюса ::= input | output | polus
тело описания структуры ::= описание переменных операторы
построения структуры
описание переменных ::= {описание переменных одного типа;}
описание переменных одного типа ::= имя типа идентификатор {,
идентификатор}
операторы построения структуры ::= {оператор построения структуры;}
оператор построения структуры ::= оператор присваивания | оператор
цикла | условный оператор | оператор задания полюса структуры
оператор присваивания ::= имя = выражение
оператор цикла ::= for идентификатор = целочисленное выражение to
целочисленное выражение do оператор построения структуры endf
условный оператор ::= if выражение then оператор построения
структуры else оператор построения структуры endi | if выражение then
оператор построения структуры endi
выражение ::= слагаемое {бинарная операция приоритета 1 слагаемое}
слагаемое ::= множитель {бинарная операция приоритета 2 множитель}
множитель ::= имя переменной | имя константы | обращение к функции
| (выражение)
имя переменной ::= имя
имя константы ::= число | имя графовой константы
имя графовой константы ::= спецификатор типа графа (целочисленное
выражение {,целочисленное выражение}) [(список имен вершин)]
спецификатор типа графа ::= compl | nc | path | cycle | rectan | star | bipart | tree
| dpath | dcycle | drectan | dstar | dbipart | dtree
список имен вершин ::= имя группы вершин {,имя группы вершин}
имя группы вершин ::= идентификатор [ [диапазон индексов {,диапазон
индексов}] ] [ ( список имен полюсов ) ]
бинарная операция приоритета 1 ::= + | - | == | <> | -> | <бинарная операция приоритета 2 ::= * | ** | \
обращение к функции ::= идентификатор (выражение{,выражение})
оператор задания полюса структуры ::= спецификатор полюса имя =
(список имен вершин)
structure S def S = compl(5)(a, b, c, d, e) endstr
structure Sa def Sa = path(5)(a(p2), b(p1, p2), c(p1, p2), d(p1, p2), e(p1)) endstr
structure Sb def Sb = cycle(n)(b[0: n – 1](p1, p2)) endstr
structure K(integer m, n) def K = rectan(m*n)(V[1:m, 1:n]) endstr
structure L1(integer m, n) def L1 = bipart(m+n)(U[1:m], W[1:n]) endstr
structure L2(integer m, n) def L2 = bipart(m+n)(U[1:m](P[1:n]), W[1:n]( P[1:m]))
endstr
structure M(input X; output Y) def M = compl(5)(a, b, c, d, e); X = (a); Y = (d,e)
endstr
Пример кода соответствующий описанию:
structure Torus def
Torus = rectan(4,4)(P[1:4, 1:4](A, B, C, D));
for i=1 to 4 do
Torus = Torus + (P[1,i](B) <> P[4,i](D))
+ (P[i,1](A) <> P[i,4](C))
endf
endstr
Описание процесса
описание процесса ::= process идентификатор [параметры] [интерфейс]
тело описания процесса endpro
параметры ::= (описание параметров одного типа {; описание параметров
одного типа})
описание параметров одного типа ::= имя типа
идентификатор{,идентификатор}
интерфейс ::= (элемент интерфейса {; элемент интерфейса})
элемент интерфейса ::= спецификатор полюса список имен полюсов
список имен полюсов ::= имя группы полюсов {,имя группы полюсов}
имя группы полюсов ::= идентификатор [ [диапазон индексов
{,диапазон индексов}] ]
диапазон индексов ::= целочисленное выражение : целочисленное
выражение | целочисленное выражение
имя ::= идентификатор [ [индекс {,индекс}] ] [ (идентификатор [
[индекс {,индекс}] ] ) ]
индекс ::= целочисленное выражение
спецификатор полюса ::= input | output | polus
тело описания процесса ::= инициализация описание события
{описание события}
инициализация ::= initial описание переменных {оператор;} endin
описание переменных ::= {описание переменных одного типа;}
описание переменных одного типа ::= имя типа идентификатор {,
идентификатор}
оператор ::= оператор присваивания | оператор цикла | условный
оператор | оператор планирования события | оператор передачи
сообщения | оператор вывода
Примечание: Оператор передачи сообщения обеспечивает передачу
сообщения внутри модели по связям (дугам и ребрам). Оператор вывода
обеспечивает передачу данных из модели во внешнюю среду, например, в
выходной файл, на экран монитора, в базу данных.
оператор присваивания ::= имя = выражение
оператор цикла ::= for идентификатор = целочисленное выражение to
целочисленное выражение do оператор endf
условный оператор ::= if выражение then оператор else оператор endi |
if выражение then оператор endi
выражение ::= слагаемое {бинарная операция приоритета 1 слагаемое}
слагаемое ::= множитель {бинарная операция приоритета 2 множитель}
множитель ::= имя переменной | константа | обращение к функции |
(выражение)
имя переменной ::= имя
константа ::= число | строковая константа
описание события ::= event имя; {оператор;} ende
оператор планирования события ::= let имя be in числовое выражение
оператор передачи сообщения ::= out выражение via имя
бинарная операция приоритета 1 ::= + | - | == | =/= |
бинарная операция приоритета 2 ::= * | ** | \
обращение к функции ::= идентификатор (выражение {,выражение})
Описание модели
описание модели ::= model идентификатор [параметры] [интерфейс]
def тело описания модели endmod
параметры ::= (описание параметров одного типа {; описание параметров
одного типа})
описание параметров одного типа ::= имя типа
идентификатор{,идентификатор}
интерфейс ::= (элемент интерфейса {; элемент интерфейса})
элемент интерфейса ::= спецификатор полюса список имен полюсов
список имен полюсов ::= имя группы полюсов {,имя группы полюсов}
имя группы полюсов ::= идентификатор [ [диапазон индексов
{,диапазон индексов}] ]
диапазон индексов ::= целочисленное выражение : целочисленное
выражение | целочисленное выражение
имя ::= идентификатор [ [индекс {,индекс}] ] [ (идентификатор [
[индекс {,индекс}] ] ) ]
индекс ::= целочисленное выражение
спецификатор полюса ::= input | output | polus
тело описания модели ::= описание переменных операторы построения
модели
описание переменных ::= {описание переменных одного типа;}
описание переменных одного типа ::= имя типа идентификатор {,
идентификатор}
операторы построения модели ::= {оператор;}
оператор ::= оператор присваивания | оператор цикла | условный
оператор | оператор задания структуры | оператор задания процесса
оператор присваивания ::= имя = выражение
оператор цикла ::= for идентификатор = целочисленное выражение to
целочисленное выражение do оператор endf
условный оператор ::= if выражение then оператор else оператор endi |
if выражение then оператор endi
выражение ::= слагаемое {бинарная операция приоритета 1 слагаемое}
слагаемое ::= множитель {бинарная операция приоритета 2 множитель}
множитель ::= имя переменной | имя константы | обращение к функции
| (выражение)
имя переменной ::= имя
имя константы ::= число | строка
оператор задания структуры ::= structure(имя) = идентификатор
[фактические параметры] [связь интерфейсов]
фактические параметры ::= (выражение {, выражение})
связь интерфейсов ::= сопоставление имен {,сопоставление имен}
сопоставление имен ::= имя группы полюсов = имя группы полюсов
оператор задания процесса ::= process(составное имя) = идентификатор
[фактические параметры] [связь интерфейсов]
составное имя ::= идентификатор.имя
Приложение 3
class LexicalAnalyser
private:
{
unsigned int CurrentLine; // текущая строка
int
CommentFlagCount;
// флаг комментария
vector<TokenPosition> tokens; // массив токенов с номером их строк
vector<Error> errors; // массив ошибок с номером их строк
map<string, Token> lexemes; // лексемы языка
map<string, Token> variableTypes; // типы переменных
map<string, Token> variables; // перменные
map<string, Token> constants; // константы
void forbiddenSigns(string s); // запрещенные символы
void initializeLexemes(); // инициализация лексем. в коде можно найти id каждой
лексемы
void addError(string s);
// добавление ошибки в массив errors
void addToken(LexemeType tokenMap, string s); // добавление токена в массив
tokens
void addVariable(string s); // добавление переменной
void addConstant(string s); // добавление константы
void checkTokens();
LexemeType
wordsLexemeType(string s); // определение типа лексемы
string deleteComments(string s); // удаляет комментарии "//" и "/* */" из строки
string selectWord(string s, unsigned int pos, char c); // выбирает слово из
строки с позиции pos до символа c
string trimSpaces(string s); // удаляет лишние вхождение пробелов в строке
string addSpaces(string s); // добавляет пробелы между знаками пунктуации. для
удобства разбора строки
vector<string> pieceString(string s, char c); // разбирает строку s на слова,
разделенные char c
bool isLetter(char c); // true, если c буква
bool isPunctuation(char c); // true, если с знак пунктуации
bool isNumber(char c); // true, если c цифра
bool isSign(char c); // true, если c какой-либо знак
bool isVariableName(string s); // true, если s удовлетворяет требованию на
ограниченность имен переменных
bool isVariableType(string s); // true, если s является типом данных
bool isLexeme(string s); // true, если s какая-либо лексема
bool isConstant(string s); // true, если s константа
bool doesExistLexeme(string s); // true, если существует ячейка s массиве lexemes
bool doesExistVariable(string s); // true, если существует ячейка s массиве
variables
bool doesExistConstant(string s); // true, если существует ячейка s массиве
constants
public:
LexicalAnalyser(char* fileName); // конструктор. считывает из файла код и создает
поток токенов
vector<string> getErrors(); // возвращает массив с ошибками как конечную строку
vector<TokenPosition> getTokens(); // возвращает массив токенов
bool isOK(); // true, если лексический анализатор не обнаружил ошибок
};
Приложение 4
Таблица соответствий кодов лексем входного языка моделирования:
Для идентификаторов устанавливается код id=599; все константы с
неопределенным типом получают код id=699; для удобства при определении
типа констант и переменных, присваивается id соответствующего id типа,
например, int A будет иметь ("A", 503), "234" ("234", "short").
коды типов (для констант)
" char "
100 (600)
" short "
101 (601)
" int "
102 (602)
" long "
103 (603)
" float "
104 (604)
" double "
105 (605)
спецификаторы типов графов
" compl "
110
" nc "
111
" path "
112
" cycle "
113
" rectan "
114
" star "
115
" bipart "
116
" tree "
117
" dpath "
118
" dcycle "
119
" drectan "
120
" dstar "
121
" dbipart "
122
" dtree "
123
спецификаторы полюса
" input "
500
" output "
501
" polus "
502
лексемы структур
" structure "
200
" def "
201
" endstr "
202
лексемы цикла
" for "
210
" to "
211
" do "
212
" endf "
213
лексемы условного оператора
" if "
220
" then "
221
" else "
222
" endi "
223
лексемы процесса
" process "
230
" endpro "
231
" initial "
240
" endin "
241
лексемы события
" event "
250
" ende "
251
лексемы оператора планированяи
события
" let "
260
" be in "
261
" out "
270
" via "
271
лексемы модели
" model "
280
" endmod "
281
" -> "
301
бинарные операторы приоритета 1
"+"
310
"-"
311
" == "
312
" =/= "
313
" <> "
314
"<"
315
">"
316
"="
300
бинарные операторы приоритета 2
" ** "
321
"*"
320
"/"
317
"\"
322
пунктуация
";"
400
":"
401
","
402
"("
403
")"
404
"["
405
"]"
406
"."
407
Download