.ps +1
.vs +1
.start
.ds C \f[B]C\f[]
.title "Проект по компютърни системи за управление на роботи"
.title "Език за улесняване на програмиране на вградени устройства"
.author "Галин Симеонов Ф.Н. 81635" "gtsimeonov@uni-sofia.bg"
.heading "Увод"
.paragraph
При програмирането на вградени устройства,
някои от проблемите се моделират чрез автомати.
Също е практика да се програмира на езици от ниско ниво, като \*[C],
при които ръчното имплементиране на автомати с каквито и да е размери
често е неинтуитивно, предразполагащо към грешки и трудоемко.
С цел да помогна разработването на програми, чийто модел е ориентиран околу
състоянията на системата, предлагам и имплементирам експериментален миниатюрен език транспилиращ до \*[C].
.paragraph
За цели имам той да е прост, интуитивен и евентуално лесен за генериране от инстурменти с графичен интернфейс
.footnote
Макар, че директното генериране на код без този език да действа като посредник би било по-смислено.
.footnote end
\&.
.heading "Описание на езика"
.heading "Общ поглед" 2
Една 'програма' съдържа един или повече 'машини', които действат като контейнери за състояния,събития и преходи.
Всяка машина си има име и има отделно пространство за имена на състояния и събития.
Чрез преходите може да се извикват външни функции.
Ето най-простият пример за това как изглежда кодът:
.code
machine light_bulb
[
states [ ON , OFF ];
events [ TURN_ON , TURN_OFF , SWITCH_STATE ];
starting on OFF;
transitions
[
from ON to OFF on event TURN_OFF;
from ON to OFF on event SWITCH_STATE;
from OFF to ON on event TURN_ON;
from OFF to ON on event TURN_ON;
];
];
.code end
След транспилация се генерират 3 файла - xxx.h xxx.c и xxx_external.h, който съответно съдържат
декларациите на служебните функции, тяхната имплементация и декларациите на външните функции, които са използвани от някой преход.
Подаването на събития към тези 'автомати' става посредством служебната функция - \f[B]push_event_to_machine\f[]
Горният пример не е много функционален, защото комуникацията е само в една посока.
За да може да връща информация и да има функционалност, на всеки преход може да се сложат функции, които да бъдат изпълнени преди преходът да е завършил
.footnote
Това свойство е важно и авторът се е стремил да го запази. Това позволява, например, да се подават събития от функции изпълнени по време на преход
.footnote end
\&.
Taка горният пример може да бъде преработен да вика функция, която действително да включва и изключва някаква лампа:
.code
machine light_bulb
[
states [ ON , OFF ];
events [ TURN_ON , TURN_OFF , SWITCH_STATE ];
starting on OFF;
transitions
[
from ON to OFF on event TURN_OFF
execute light "off";
from ON to OFF on event SWITCH_STATE
execute light "off";
from OFF to ON on event TURN_ON
execute light "on";
from OFF to ON on event TURN_ON
execute light "on";
];
];
.code end
Тук също е демонстрирано как се подават аргументи към тези функции.
Символните низове са избрани като единствен начин да се подават аргументи, защото е предвидено да се транспилира до езици, различни от \*[C],
а въвеждането на типова система или нещо, което да описва различните аргументи би натоварило езикът твърде много и би донесло само минимална печалба.
.paragraph
Възможно е да има повече от една 'машина' в кодът. Например, нека към горния пример добавим:
.code
machine light_controler
[
states [ STATIC , BLINKING ];
events [ SIGNAL , GO_STATIC, START_BLINKING ];
starting on STATIC;
transitions
[
from STATIC to BLINKING on event START_BLINKING;
from BLINKING to STATIC on event GO_STATIC;
from BLINKING to BLINKING on event SIGNAL
execute prod_light_bulb;
];
];
machine timer
[
states [ ON , OFF ];
events [ TICK , START , STOP ];
starting on OFF;
transitions
[
from ON to OFF on event STOP;
from OFF to ON on event START
execute prod_timer;
from ON to ON on event TICK
execute prod_timer | prod_light_controler;
];
];
.code end
Тук може да се види и 'навързване' на различни функции.
timer 'машината' праща сигнал до себе си и до light_controler, а light_controler праща сигнал до light_bulb.
Тук можем да прескочим предаването на събития между 'машините' чрез използването на \f[B]if\f[].
Променяме третия преход от timer:
.code
from ON to ON on event TICK
if(light_controler.BLINKING)
execute prod_timer | prod_light_bulb;
.code end
Това е условно изпълнение на командите, преходът се случва и състоянието е променено независимо от истинността на условието.
За реализацията на условен преход, в езикът има \f[B]granted\f[] ключовата дума.
Горното може да се опитаме да имплементираме по следния начин:
.code
from ON to ON on event TICK granted (light_controler.BLINKING)
execute prod_timer | prod_light_bulb;
.code end
Тук има проблема, че timer спира да работи ако light_controler не е в състояние BLINKING,
защото преходът няма да се случи и \f[B]execute prod_timer\f[] няма да се изпълни.
.footnote
Това, че този пример може да бъде имплементиран на \*[C] под 10 реда, е забелязано от автора.
.footnote end
.paragraph
\f[B]prod_timer\f[],\f[B]prod_light_bulb\f[],\f[B]prod_timer\f[] и \f[B]light\f[] са функции, чиято имплементация трябва да бъде предоставена от програмиста.
Всички външни функции се събират и се записват в генерирания xxxx_exter.h файл. Този пример би генерирал:
.code
#ifndef XXXX_EXTERN_H
#define XXXX_EXTERN_H XXXX_EXTERN_H
extern machine_buffer_t* light(machine_buffer_t *arguments,machine_buffer_t *input);
extern machine_buffer_t* prod_light_bulb(machine_buffer_t *arguments,machine_buffer_t *input);
extern machine_buffer_t* prod_light_controler(machine_buffer_t *arguments,machine_buffer_t *input);
extern machine_buffer_t* prod_timer(machine_buffer_t *arguments,machine_buffer_t *input);
#endif
.code end
Ето и една примерна тяхна импелементация заедно с \f[B]main\f[] функцията:
.code
machine_buffer_t* light(machine_buffer_t *arguments,machine_buffer_t *input)
{
printf("light %s\n",arguments->buffer);
return NULL;
}
machine_buffer_t* prod_light_bulb(machine_buffer_t *arguments,machine_buffer_t *input)
{
push_event_to_machine(light_bulb,light_bulb_EVENT_SWITCH_STATE,NULL);
return NULL;
}
machine_buffer_t* prod_light_controler(machine_buffer_t *arguments,machine_buffer_t *input)
{
push_event_to_machine(light_controler,light_controler_EVENT_SIGNAL,NULL);
return NULL;
}
machine_buffer_t* prod_timer(machine_buffer_t *arguments,machine_buffer_t *input)
{
push_event_to_machine(timer,timer_EVENT_TICK,NULL);
sleep(1);
return NULL;
}
int main()
{
push_event_to_machine(light_controler,light_controler_EVENT_START_BLINKING,NULL);
push_event_to_machine(timer,timer_EVENT_START,NULL);
return 0;
}
.code end
Това ни дава изхода:
.code
light on
light off
light on
light off
light on
light off
light on
light off
light on
\&...
\&...
.code end
.heading "Формално описание на езика" 2
Със затъмнените думи и символи обозначавам думи и символи, които трябва да се интерпретират директно.
.heading "Програма" 3
.right
.nf
програма : машина [ програма ]
.fi
.right end
Програмата е поредица от машини. Всяка машина има уникално име.
.heading "Машина" 3
.right
.nf
машина : \f[BI]machine\f[] име \f[BI][\f[] вътрешна част на машината \f[BI] ] ; \f[]
вътрешна част на машината : \f[BI]states [\f[] поредица от състояния \f[BI] ] ; \f[] [ вътрешна част на машината ]
\f[BI]events [\f[] поредица от събития \f[BI] ] ; \f[] [ вътрешна част на машината ]
\f[BI]transitions [\f[] поредица от преходи \f[BI] ] ; \f[] [ вътрешна част на машината ]
\f[BI]starting on \f[] име на състояние \f[BI];\f[] [ вътрешна част на машината ]
.fi
.right end
В една машина може да се срещне само веднъж декларация на състоянията, събитията, преходите и посочване на стартиращо състояние.
Декларацията на стартиращо състояние трябва да е след декларацията на състоянията. То трябва да е сред декларираните състояния.
Декларацията на преходите трябва да е след декларациите на състоянията и на събитията.
Сред декларираните състояния и събития не трябва да има повтарящи се.
Сред преходите не трябва да има две различни, които излизат от едно състояние и имат за етикет едно събитие.
.heading "Преход" 3
.right
.nf
преход : \f[BI] from \f[] име-на-състояние
\f[BI] to \f[] име-на-състояние \f[BI] on \f[] име-на-събитие [ опашка-на-прехода ] \f[BI];\f[]
опашка-на-прехода : [ \f[BI] granted \f[] израз ] [ условно-изпълнение ]
условно-изпълнение : \f[BI] if \f[] израз условно-изпълнение [ \f[BI] else \f[] условно-изпълнение ]
условно-изпълнение : \f[BI] execute \f[] опашка на изпълнението
опашка-на-изпълнението : име-на-външна-функция \f[BI]"\f[]символен-низ\f[BI]"\f[] [ \f[BI] | \f[] опашка-на-изпълнението ]
.fi
.right end
Ако изразът след \f[BI]granted\f[] е истина то преходът се реализира и командите в условното изпълнение се изпълняват спрямо семантиката,
иначе преходът не се изпълнява и опашката на преходът не се изпълнява.
Ако изразът след \f[BI]if\f[] е истина то условното изпълнение след изразът се изпълнява, иначе, ако има \f[BI]else\f[]
съответстващ на \f[BI]if\f[]-а то условното узпълнение след \f[BI]else\f[] се изпълнява.
Ако условното изпълнение е от типа започващ с \f[BI]execute\f[] то външните функции се изпълняват в ред на срещане
като изходът на всяка се подава на следващата. Изходът на последната изпълнена функция се изхвърля.
.heading "Израз" 3
.right
.nf
израз : израз-или
израз-или : израз-и [ \f[BI]|| \f[] израз-или ]
израз-и : израз-не [ \f[BI]&& \f[] израз-и ]
израз-не : [\f[BI]!\f[]]базов-израз
базов-израз : име-на-машина\f[BI].\f[]име-на-състояние | \f[BI](\f[]израз\f[BI])\f[]
.fi
.right end
Израз може да се оцени до истина или лъжа.
Логическите оператори имат обичайната семантика.
В базовия израз е позволено да се посочват състояния на други машини, но не е позволено да се посочват състояния на машината,
в която се намира изразът. Тези посочвания се оценяват до истина ако посочената машина е заела посоченото състояние.
Могат да се посочват имена на машини, които са декларирани след сегашната, но те трябва да съществуват. Това важи и за състоянията, те трябва също и да принадлежат на посочената машина.
.heading "Детайли на имплементацията"
За да се реализира обмена на информация между генерирания код и написания,
е дефинирана структура \f[B]machine_buffer_t\f[], в която се записват данните и техният размер.
Генерират се и няколко помощни функции, които улесняват работата с такива структури.
За да се запази свойството - командите на преходът да се изпълнят преди състоянието да се смени,
се използва опашка, в която се записват
.heading "Описание на командните аргументи"
Имплементацията на този език предадена от автора приема следните аргументи.
.code
--print-tokens
.code end
Извежда разпознатите лексеми
.code
--print-ast
.code end
Извежда разпознатите структури в текста. ( Абстрактното синтактично дърво )
.code
-o име-на-файл | --output име-на-файл
.code end
Посочва префиксът на генерираните файлове. Например \f[B]xxxx.h xxxx.c xxxx_external.h\f[].
.code
--extern-mutex
.code end
Добавя мутекс преди и след подаването на събитие. Този мутекс трябва да се имплементира външно.
.code
--extern-queue
.code end
Дава възможност на програмиста да даде собствена имплементация на опашката използвана при задържането на събития.
.code
--extern-buffer
.code end
Дава възможност на програмиста да даде собствена имплементация на структурата използвана за пренос на данни.
.heading "Забележки"
За имплементацията е използвана само стандартната \*[C] библиотека, което би помогнало този транспилатор да бъде
компилиран до много операционни системи.
.heading "Бъдещи насоки"
.list
.item
Да се добави ключова дума \f[B]signal\f[] която да праща сигнал до определена машина, за да не трябва да го имплементира програмистът.
.item
Да се добави семанитка за състояния и събития, като например - 'при пристигане до това състояние изпълни ... '.
.item
Да се направи така, че отделните машини да могат да бъдат на различни физически устройства, т.е. да бъде направен разпределен.
.item
Да се направи на пълен език за програмиране.
.list end
.finish