В предыдущих лекциях уже были рассмотрены некоторые визуальные языки - UML и BPMN. Однако они слишком большие и сложные, чтобы на их примере учиться создавать собственные DSL. Поэтому в этой лекции будет представлен небольшой, "игрушечный" визуальный язык. Он позволяет описать множество компонент с атрибутами и методами, набор сообщений, которыми могут обмениваться экземпляры компонент, а также конечные автоматы, описывающие поведение каждой компоненты. В дальнейшем я буду называть этот язык SCL (Simple Component Language). Пример SCL-модели показан на рис. 12.1.
(рис 12.1) Пример SCL-модели
На рис. 12.1, а представлены три компоненты - Client, Server и Monitor, - а также список сигналов, которыми они могут обмениваться. Экземпляры компонент взаимодействуют друг с другом через посылку/прием сигналов из этого списка. В SCL для упрощения у компонент нет интерфейсов, а все сигналы глобальные. Все компоненты их "видят", а значит, могут использовать.
Поведение компонент представляется с помощью конечных автоматов - пример для компоненты Server показан на рис. 12.1, б. Монитор (экземпляр компоненты Monitor) создается автоматически, при запуске всей системы, о чем свидетельствует на рис. 12.1, а первый параметр после его имени, равный единице. Монитор создает экземпляр компоненты Server (далее - сервер) и экземпляр компоненты Client (далее - клиент). Экземпляры этих компонент создаются монитором, а не по умолчанию, при запуске системы. Поэтому первый параметр после имен этих компонент равен нулю. В этой небольшой "игрушечной" системе может существовать не более одного клиента и сервера, на что указывают вторые параметры после имен этих компонент, равные единице.
На рис. 12.1, б представлено поведение сервера. Сервер, после того, как его создаст монитор, оказывается в состоянии Start и вызывает свою процедуру Init(). Эта процедура выполняет все служебные действия по инициализации сервера. Далее он переходит в состояние Idle, в котором готов обслужить запросы клиента. При получении такого запроса (сигнал Request) сервер оповещает клиента с помощью сигнала Respond о том, что запрос до него дошел и требуется дополнительная информация. Клиент посылает либо сигнал Info1, либо сигнал Info2. В зависимости от этого сервер или принимает запрос на обработку, или нет. В первом случае он посылает клиенту сигнал Accept и вызывает свою процедуру-обработчик запроса AcceptHandle(), во втором случае он посылает клиенту сигнал Reject. В обоих
случаях сервер переходит в состояние Idle и готов обрабатывать следующие запросы. В этом же состоянии
сервер может обработать сигнал монитора Terminate, присылаемый ему при необходимости прекратить работу. В этом случае, для корректного завершения своей работы сервер вызывает процедуру TerminateHandle(). После ее завершения сервер переходит в состояние End и окончательно завершается. Нетрудно продолжить этот пример, дописав конечные автоматы для клиента и монитора.
Наверное, про этот язык понятно все или почти все прямо из приведенного выше примера. Однако так происходит потому, что SCL очень прост. Если в него добавить связи между компонентами, параметры сигналов, ветвления в переходах и т. д., то он бы существенно усложнился и возникла бы потребность в том, чтобы создать его точное описание - определить синтаксис, семантику и
Здесь не будет дано точных определений формального языка, грамматики, SCL и создавать что-то подобное им самостоятельно.
Грамматика языка в форме Бэкуса-Наура задает структуру текстов, которые можно создавать с помощью этого языка. Строгое определение этой структуры позволяет формальную обработку таких текстов - обнаружение синтаксических ошибок, валидацию более сложных ограничений, генерацию программного кода и т.д.
Структура текстов определяется иерархически, в виде правил. В случае с SCL любой текст (визуальная модель) должен состоять из имени модели, списка сигналов и набора компонент:
<model> :: = <model name><signal_list>+ <component>+
Конструкция <model> называется <signal_list>. Таких списков, равно как и компонент (<component>), в модели может быть сколько угодно, но обязательно должен быть хоть один, что изображается значком + рядом с тем элементом грамматики, которого "может быть один или много".
Список сигналов устроен следующим образом. Он состоит из строк, обозначающих сигналы, которые могут быть посланы и получены экземплярами компонент:
<signal_list> :: = <signal>+ <signal> :: = <signal name>
Конструкции, подобные <signal name> и <model name> обычно называются SCL идентификаторы являются <model>, <signal_list>, <signal>, <component>. В SCL-грамматике
$, #, @ и пр. Однако здесь я не стал усложнять простой пример. Для тех, кто хочет более основательно разобраться в теории формальных языков и грамматик, познакомиться с грамматиками реальных языков программирования, узнать, как создаются синтаксические анализаторы программ, можно порекомендовать книги [12.4], [12.5].Разобранный выше фрагмент грамматики языка SCL можно представить деревом, как показано на рис. 12.2.
(рис 12.2) Фрагмент грамматики языка SCL в виде дерева
Продолжим изучать грамматику языка SCL. Конструкция <component> определяется следующим образом:
<component> :: = <component name> <parameters> <method>* [<statechart>] <attribute>*
Видно, что компонента состоит из имени ( <component name> ), параметров ( <parameters> ), набора методов ( <method> ) и атрибутов ( <attribute> ), а также конечного автомата ( <statechart> ). Сразу за нетерминалами <method> и <attribute> следует значок '* ', который указывает на то, что как методов, так и атрибутов в компоненте может быть произвольное количество, в том числе и не быть вовсе. Последняя оговорка отличает символ '* ' от '+ '. <statechart> взят в квадратные скобки. Это означает, что его может не быть, т. е. конечный автомат может у компоненты отсутствовать.
У компоненты есть два параметра, следующих в круглых скобках за ее именем. Первый параметр указывает на то, сколько экземпляров компонент создается при запуске системы, второй - сколько экземпляров одновременно может существовать в системе:
<parameters>::= (<init>, <num>)
Круглые скобки и запятая в представленном выше правиле являются новым видом терминальных элементов, в отличие от угловых и квадратных скобок, которые являются частью нотации самой грамматики. Круглые скобки и другие подобные элементы появляются в связи с тем, что графические символы в SCL могут быть "нагружены" текстом, посредством которого определяются различные свойства графических конструкций (т. е. текст для SCL - это не просто строка).
Конечный автомат включает в себя <component_ref> ), а также набор состояний ( <state>+ ):
<statechart> ::= <component_ref> <state>+
Прокомментируем элемент <component_ref>. Эта конструкция похожа на идентификатор, т. е. тоже является строковым значением и
Состояние устроено так:
<state> ::= <state name> <transition>*
Видно, что у него есть имя, а также исходящие переходы, которые переводят экземпляры данной компоненты из этого состояния в другие.
Переход, в свою очередь, устроен следующим образом:
<transition> :: = [<input>]/<action> {; <action>}* <target_state_ref>
Он инициируется определенным сигналом, который принимается и обрабатывается компонентой в этом состоянии (конструкция <input> ), содержит в себе ряд действий ( <action> ) и завершается новым состоянием, в которое экземпляр компоненты переходит, обработав данный входной сигнал (конструкция <target_state_ref> ). В конструкции <transition> содержатся два терминала нового типа, которые еще не рассматривались в этой лекции. Это значок '/ ', который отделяет входной сигнал от действий по его обработке, и символ '; ', разделяющий действия в переходе в том случае, если их более одного. Эти терминалы относятся к тому же типу, что и круглые скобки. Отметим, что фигурные скобки, так же как и квадратные, являются частью грамматики. Они группируют аргумент для операции, обозначаемой символом '*': следующее действие в переходе должно быть отделено символом ';'
от предыдущего, в то время как последнее действие не должно иметь после себя этот разделитель.
Действие в переходе может быть либо
<action> :: = <method_invocation>| <send>
Значок '|' SCL-грамматики обозначает альтернативу.
Ниже представлена вся SCL-грамматика целиком, с некоторыми добавлениями, которые мне кажутся очевидными.
<model> :: = <model name><signal list>+ <component>+ <component> :: = <component name> <parameters> <method>* [<statechart>] <attribute>* <parameters>::= (<init>, <num>) <statechart > ::= <component_ref><state>+ <state> ::= <state name> <transition>* <transition> :: = [<input>]/<action> (; <action>)* <target_state> <action> :: = <method_invocation>| <send> <method> :: = < method name>() <attribute> ::= <attribute name> <attribute_type> <attribute_type> :: = <attribute type name> <target_state> :: = <state_ref> <send>::= <signal_ref> <input>::= <signal_ref> <method_invocation > :: = <method_ref>()
UML, либо какие-то более специализированные формализмы, как например, в Microsoft DSL Tools.
На рис. 12.3 представлена SCL. Посмотрим, как она соответствует построенной выше грамматике в форме Бэкуса-Наура.
(рис 12.3) Метамодель языка SCL
Все 0..*, 1..*, 0..1 соответственно. Конец ассоциаций со значком агрегирования во многих случаях назван именем Owner. В случае класса component даны имена противоположным концам агрегирования. Имена концов ассоциаций понадобятся для составления формальных ограничений на <state_ref> у <statechart> ) роль такой ссылки выполняет агрегирование - по этой связи можно добраться от конечного автомата до его компоненты-владельца.
Некоторая информация об абстрактном синтаксисе SCL не вошла ни в грамматику, ни в OCL.
context handle_invocation inv: self.method_ref.owner == self.owner.owner.owner. owner.owner
context component
inv: not (self.attributes-> isEmpty() and
self.methods ->isEmpty() and
self.statecharts->isEmpty())
context component inv: self.init >=0 and self.init < = self.num
Попытка выразить эту информацию на SCL (попытайтесь это сделать в качестве упражнения!).
Теперь несколько слов о самом языке OCL. Каждое OCL-утверждение должно иметь контекст - класс, ассоциацию или операцию. Например, первое утверждение из представленных выше примеров читается так: для любого экземпляра класса handle_invocation справедливо следующее утверждение… Ключевое слово self обозначает экземпляр той сущности self.init ), а также к противоположным концам ассоциаций, если те имеют имена (ведь концы ассоциаций очень похожи на атрибуты класса, как обсуждалось в лекциях по UML ). Через имена концов ассоциаций можно "ходить" по модели и таким образом расширять контекст, к которому применяется OCL-ограничение. Но исходная точка, от которой происходит "хождение" и к которой в любой момент можно вернуться через ключевое
слово self, неизменна для всего утверждения.
Язык OCL имеет ряд вcтроенных функций (например, функция isEmpty ), а также средства для работы с коллекциями, квантор всеобщности с заданием связной переменной и т. д.
В средах типа Microsoft DSL Tools из языка OCL взята лишь сама идея - накладывать логические ограничения на C#. Это гораздо практичнее, так как иначе пользователям среды пришлось бы дополнительно изучать новый язык ( OCL ), а авторам - создавать для него транслятор все в тот же С#.
Одним из способов задать конкретный синтаксис -
В SCL существуют следующие графические символы:
(рис 12.4)
contains - is associated with - is followed by - is connected to - set - Определение диаграммы компонент SCL может выглядеть следующим образом:
<component_diagram> ::= {<signal_list>+ <component>+
<statechart_diagram>+} set
Это означает, что на диаграмме компонент должен присутствовать один или более списков сигналов, один или более компонент диаграмм конечных автоматов для компонент, и все эти конструкции располагаются в произвольном порядке.
Определение компоненты можно изобразить так:
<component> ::= <component_symbol> contains
{component_name><parameters> <attribute_area> <method_area>}
Графический символ компоненты содержит область, структура которой задана вторым операндом оператора contains: имя компоненты и ее параметры, набора атрибутов и методов. Диаграмма конечных автоматов (конструкция <statechart_diagram> определяется так:
<statechart_diagram> ::= {<statechart_symbol> contains
{ <component_ref> {<state>+} set}
То есть в прямоугольнике <statechart_diagram> сначала можно увидеть имя компоненты, к которой относится этот автомат (конструкция <component_ref> ), а затем идет и сам автомат (что точно обозначает "сначала" и "затем" не определяются грамматикой; считается, что они однозначно определяются либо по умолчанию, либо контекстом, либо, если все таки возникает неоднозначность, то это не важно…). Автомат состоит из состояний, которые расположены на диаграмме в произвольном порядке (это задает оператор set ).
Cостояние определяется так:
<state> ::= {<state_symbol> contains <state name>}is associated
with <input_area>
Эта строка читается следующим образом: <state_symbol> содержит в себе <state name> и находится ближе всех других графических символов к графическому агрегату <input_area>.
Конструкция <input_area> определяется так:
<input_area>::= {[<input>]/<action> {; <action>}*}} is associated
with <transition_area>
Она состоит из текстовой надписи, которая задается фрагментом грамматики [<input>]/<action> {; <action>}*}, и графического агрегата <transition_area>. При этом надпись "прилеплена" к <transition_area>.
Конструкция <transition_area> задается как линия, конец которой присоединен к графическому агрегату <next_state_area>:
<transition_area>::= <transition_symbol> is connected to <next_state_area>
Конструкция <next_state_area> – это символ состояния с текстовым именем внутри:
<next_state_area> ::= <state_symbol> contains <state name>
Выше были представлены лишь фрагменты, которые нужно формально соединить с грамматикой абстрактного синтаксиса SCL. Это не было выполнено, чтобы не
Как обсуждалось в предыдущей лекции,
XML-схема в формате XML Schema для SCL выглядит следующим образом.
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault= "qualified" attributeFormDefault="unqualified">
<xs:complexType name="model">
<xs:sequence>
<xs:element name="component" type="component" maxOccurs="unbounded"/>
<xs:element name="signal_list" type="signal_list" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="signal_list">
<xs:sequence>
<xs:element name="signal" type="signal" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="component">
<xs:sequence>
<xs:element name="attribute" type="attribute" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="method" type="method" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="statechart" type="statechart" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="init" type="xs:string"/>
<xs:attribute name="num" type="xs:string"/>
</xs:complexType>
<xs:complexType name="signal">
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
<xs:complexType name="attribute">
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="type" type="xs:string"/>
</xs:complexType>
<xs:complexType name="method">
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
<xs:complexType name="statechart">
<xs:sequence>
<xs:element name="state" type="state" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="state">
<xs:sequence>
<xs:element name="transition" type="transition" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
<xs:complexType name="transition">
<xs:sequence>
<xs:element name="input" type="input" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="send" type="send"/>
<xs:element name="handle_invocation" type="handle_invocation"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="target_state_ref" type="xs:string"/>
</xs:complexType>
<xs:complexType name="input">
<xs:attribute name="signal_ref" type="xs:string"/>
</xs:complexType>
<xs:complexType name="action"/>
<xs:complexType name="send">
<xs:complexContent>
<xs:extension base="action">
<xs:attribute name="signal_ref" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="handle_invocation">
<xs:complexContent>
<xs:extension base="action">
<xs:attribute name="method_ref" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="model" type="model"/>
</xs:schema>
Любая XML-спецификация - и схема, и документ - состоит из тегов, каждый из которых имеет имя, набор атрибутов, а также может включать в себя другие теги. Каждый тег имеет начало и конец. Теги образуют древовидную иерархию, вкладываясь друг в друга.
Будем называть теги в XML-схеме
В данном примере и абстрактный, и
XML-схема, задающая структуру XML-документов с SCL-моделями, устроена так. Сначала идет заголовок с некоторой служебной информацией. Далее следует описание complexType. Каждый из них соответствует некоторому классу component, signal, attribute и т.д. Далее идет список атрибутов, если эти атрибуты имеются у соответствующего класса в attribute, в котором через свойство name задается имя атрибута, а через свойство type – его тип. Все типы в данном примере являются строковыми (string).
complexType в XML-схеме должны быть связаны друг с другом, как связаны соответствующие им классы sequence/element, для задания наследования – choice/element и complexContent/extension.
sequence определяет, что в этом месте в
XML-документе должна идти некоторая последовательность фрагментов текста.
Тип каждого фрагмента задается тегом-типом element.
Свойства minOccurs и maxOccurs у element задают множественность вхождений фрагментов данного
типа – нижнюю и верхнюю границу соответственно. Если minOccurs
отсутствует, значит, минимальное количество таких
фрагментов равно единице. Свойство type у element
указывает тип фрагмента текста. В нашем случае это
ссылка на некоторый complexType. Ссылка
реализована по имени. sequence задает порядок
вхождений типизированных фрагментов текста. Например, если компонента
была в sequence прежде списка сигналов, то в XML-
документе, как можно видеть ниже, сначала идут все компоненты ( Client, Server, Monitor ), а потом единственный список сигналов.
Таким образом, пара sequence/element реализует
агрегирование.
choice/element и complexContent/extension нужны для задания наследования. Вспомним рис. 12.3 – класс transition агрегирует класс action (переход может включать в себя много действий), а последний является предком для классов send и handle_invocation. В XML-схеме это задается так. У тега-типа transition в sequence входит один элемент типа input (и тут все понятно), а далее можно увидеть choice. Он заменяет собой класс action и имеет множественность 0..*, как и у action. choice включает в себя два тега-типа element – для send и handle_invocation. Фрагменты текста, заданные двумя последними тегами, могут идти в произвольном порядке.
Далее определяется action и send и handle_invocation, которые строятся на основе action. Это "строительство" происходит так: в каждом из них заводится complexContent, который означает, что далее внутри идет некоторая сложная структура, сложнее тех attribute, sequence, сhoice ). Внутри complexContent определяется включение action с расширением. Это делается с помощью тега-типа extension. Текст, задаваемый action, вставляется вместо конструкции extension и расширяется дополнительным текстом – ссылкой на сигнал или ссылкой на метод для send/handle_method соответственно.
Ниже приводится соответствующий этой схеме XML-документ для графа модели, заданного на рис. 12.1. В таком виде информация о визуальной модели сохраняется на жестком диске.
<?xml version="1.0" encoding="UTF-8"?> <model> <component name="Client"> <method name="Init"/> <method name="RejectHandle"/> <method name="AcceptHandle"/> </component> <component name="Server"> <method name="Init"/> <method name="AcceptHandler"/> <method name="TerminateHandler"/> <statechart> <state name="Start"> <transition target_state_ref="Idle"> <handle_invocation method_ref="Init"/> </transition> </state> <state name="End"> </state> <state name="Idle"> <transition target_state_ref="End"> <input signal_ref="Terminate"/> <handle_invocation method_ref="TerminateHandle"/> </transition> <transition target_state_ref="Wait"> <input signal_ref="Request"/> <handle_invocation method_ref="Respond"/> </transition> </state> <state name="Wait"> <transition target_state_ref="Idle"> <input signal_ref="Info1"/> <handle_invocation method_ref="AcceptHandle"/> </transition> <transition target_state_ref="Idle"> <input signal_ref="Info2"/> <handle_invocation method_ref="Reject"/> </transition> </state> </statechart> </component> <component name="Monitor"> <method name="CreateClient"/> <method name="CreateServer"/> <method name="TerminateClient"/> <method name="TerminateServer"/> </component> <signal_list> <signal name="Request"/> <signal name="Respond"/> <signal name="Info1"/> <signal name="Info2"/> <signal name="Accept"/> <signal name="Reject"/> <signal name="Terminate"/> </signal_list> </model>
Основной семантикой SCL является SCL. Перед отправлением сигнала на обработку список текущих состояний, в которых пребывают компоненты, обновляется. Компонента может находиться не только в состоянии, но и в переходе. Тогда она не готова к приему сигналов. Переход может обрабатываться произвольное количество времени по абсолютным часам, имеющимся в системе. Все компоненты
"живут" параллельно. Детали этого параллелизма семантикой не определяются и оставлены на усмотрение реализации, например:
Предполагается, что код методов компонент описывается на языке C# и не входит в SCL. Предполагается также, что типы атрибутов описываются в соответствии с тем, как это принято в языке C#. Это же язык является целевым для генерации кода по модели, выполненной с использованием SCL.
Назначение языка SCL - учебное. Предполагается, что его пользователи - это слушатели данного курса лекций, а также студенты-исследователи, изучающие визуальное моделирование. В следующей лекции будет показано, как этот язык будет реализован в рамках Microsoft DSL Tools.
При разработке и реализации предметно-ориентированных языков главным рабочим артефактом, как правило, является
Итак, спецификация
Правило 1. Помните, что
Правило 2. Все классы XML, а также упрощает процедуры программного обхода моделей.
Правило 3. Необходимо избегать повторяющихся фрагментов в
Правило 4. Не все абстракции предметной области нужно "укладывать" в OCL.
Правило 5. Если у вас появилась ассоциация с множественностями на концах, равными по единице, то вы, скорее всего, что-то неправильно сделали. Два класса, соединенные такой ассоциацией, фактически составляют один класс. Ведь время жизни их экземпляров в точности совпадает, так что для них обоих можно определить один единственный класс. Правда, бывают исключения, но они в данном случае лишь подтверждают это правило.
Правило 6. Будьте аккуратны с заданием множественности. Точно определяйте, например, что имеется в виду под множественностью "*" - 1..* или 0..*.
Правило 7. UML и вы поймете, о чем я говорю. А если, кроме того, вы попытаетесь в ней разобраться, то, думаю, окончательно со мной согласитесь.
SCL, задав структуру идентификатора и ссылки.(i) строение идентификаторов языка; (ii) ограничения на значения атрибутов у классов (iii) связи между разными элементами языка? Приведите примеры.С++.SCL, заданные в лекции с помощью с помощью OCL, средствами <next_state_area> является самостоятельной, а в грамматике абстрактного синтаксиса ее двойник, конструкция <target_state>, является всего лишь ссылкой?UML, а также класс с секциями для имени, атрибутов и операций.XML Schema.SCL. Расскажите, что в нее не вошло, то есть осталось на усмотрение реализации языка.SCL. Охарактеризуйте их достоинства и недостатки.SCL.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.