The Russian Electronic Developer Magazine | |
Русский электронный журнал разработчика | |
Всем известны самые примитивные средства описания в виде пар "параметр=значение", столь любимые программистами в среде различных *ix (автор конечно же не поднимет руку на охаивание синтаксиса правил разбора в sendmail.cf - сами знаете за что), от которых не слишком далеко ушли структуры (синтаксис) файлов config.sys и autoexec.bat, возможное содержание которых достаточно хорошо всем знакомо. Ниже, в качестве примера, приведен кусок моего config.sys файла:
SET EPMPATH=C:\OS2\APPS;C:\opendoc\BIN; PROTECTONLY=NO SHELL=C:\OS2\MDOS\COMMAND.COM C:\OS2\MDOS FCBS=16,8 RMSIZE=640 DEVICE=C:\OS2\MDOS\VEMM.SYS DOS=LOW,NOUMB DEVICE=C:\OS2\MDOS\VXMS.SYS /UMB DEVICE=C:\OS2\MDOS\VDPMI.SYS DEVICE=C:\OS2\MDOS\VDPX.SYS DEVICE=C:\OS2\MDOS\VWIN.SYS DEVICE=C:\OS2\MDOS\VW32S.SYSКритиковать такой синтаксис несложно - он не позволяет синтаксически отделять описания разных объектов в одном файле. Зато его использование примитивно реализуется программно и во многих случаях оправдано. Но не во всех случаях возможно.
Незначительное расширение такой синтаксис получил в ini-файлах от Windows X.XX (например 3.11) за счет введения секций. Именованные секции позволяют структурировать описания и формулировать запросы на элементы описания уже по двум параметрам "имя секции"-"имя параметра", что гораздо удобнее. Вот пример из файла SYSTEM.INI для WIN-OS2 сессии:
[keyboard] subtype= type=4 keyboard.dll=kbdru.dll oemansi.bin=xlat866.bin typeofswitch=2 secondkeyb.dll=kbdusx.dll [boot.description] keyboard.typ=Enhanced 101 or 102 key US and Non US keyboards mouse.drv=Microsoft, or IBM PS/2 network.drv=No Network Installed language.dll=Russian system.drv=MS-DOS System codepage=866 woafont.fon=Russian (866)К недостаткам этого метода можно отнести синтаксическую неопределенность при работе с переменным числом записей в рамках одной секции и отсутствие вложенности секций, что иногда полезно т.к. не все информационные объекты линейны. Конечно, все это можно так или иначе реализовать программно, но эта реализация не будет естественным следствием идеи заложенной в синтаксис, а лишь свидетельством изворотливости программиста.
Здесь следует отметить, что критика и далее будет бить по одной, двум позициям намечающим путь к совершенству, не более того.
Следующим шагом в развитии идеи скриптов-описателей стали registry от Windows9X и далее, а также ini-файлы в OS/2. Привести их примеры не представляется возможным в силу их непечатности. Отметим следующее - они подразумевают древовидную структуру элементов (развитие идеи вложенности) и позволяют хранить объекты любого типа за счет введения бинарных объектов трактуемых теми, кто их использует.
Для критики существующих реализаций подобного ресурса (в OS/2 он реализован в WINPRF.DLL) необходимо уточнить требования к синтаксису файлов конфигурации вообще.
Как отмечалось ранее, взгляды, здесь излагаемые, стали результатом работы над подсистемой сопровождения сценариев имитатора-тренажера для буровиков. Сценарий для подобного тренажера содержит в себе все параметры конфигурирующие начальное состояние имитатора буровой установки и скважины, т.е. не многим отличается от файлов конфигурации. Он содержит внутри себя описание множества совершенно независимых объектов - описателей различного оборудования, описателей пород и их сочетания в геологический разрез, описания различных аномалий раскиданных по стволу скважины (чтобы обучаемому было не скучно) и т.д. и т.п.
Подобные сценарии должны были создаваться самими пользователями продукта. Отсюда возникало требование интуитивной понятности для непрограммиста самих описаний и средств их создающих. Поскольку описания одного и того же оборудования могли встречаться во многих сценариях, они должны были описываться отдельно от самого сценария и входить в него в виде ссылки. Хорошо продуманный сценарий становится методическим материалом, поэтому должны быть предусмотренны средства его печати в удобном и красивом виде.
Эти требования повлекли за собой создание цепочки информационно связанных между собой программных средств:
Выше описанные проблемы со сценарием и являются, по мнению авторов, вполне содержательной критикой использования Profile API для сопровождения сложных конфигураций.
Все это подвигло нас на формулировку следующих требований к концепции конфигурирования:
Предлагается синтаксис описания подобных классов, который оперирует ключевыми словами, литералами и коментариями.
Литералы используются такие-же как в C++ : \xx, \\,\n,\t, etc, а также специальные литералы %.
Комментарии обозначаются сочетанием символов //, /*,*/ и правила их
использования аналогичны С++. Версионность поддерживается вхождением (необязательным)
версии в имя класса -
Система оперирует следующими объектами:
Как восклицала Алиса Кэрола - "Кому нужна книжка без картинок и диалогов!"
Обратите внимание на вторую закладку - Тип. Она создается всегда и дает
возможность явно задать класс объекта и выбрать способ сохранения (текстовый/двоичный).
Последнее позволяет легко подправить руками любое поле объекта с помощью
текстового редактора. И наоборот, любой объект может быть создан в текстовм
редакторе, а затем сохранен в двоичном виде.
Для описания печати объекта используется аналогично организованная group
sections и вот, что получается при печати:
Печать объекта производится либо из его меню либо простым натаскиванием
его иконы на икону (фолдер) принтера.
Выше приведен пример печати одного объекта. Тот же объект, как ReferenceField
внутри сценария, при печати имеет следующий вид:
Как все это использовать:
Это был пример чтения конфигурации. Исходя из удобства их сопровождения
- визуализации и печати имеет смысл (в некоторых случаях) и сохранять в
них программно полученную информацию. Так, например, в буровом тренажере
журнал занятий реализован своим набором классов и в него записываются результаты
всех занятий. Вот как это реализуется:
Далее приведены их описания в предлагаемом синтакисе :
классификаторы
transient
берется из класса как значение по умолчанию; может быть изменен в каждом
представителе
базовые типы данных
int
double
string
group
namedgroupцелое (4 байта)
вещественное (8 байт)
строка символов (не обязательно ограничивается \0)
массив произвольных объектов - позволяет доступ по номеру элемента
основан на базе group, используется длп описания диалога и печати
базовые типы данных для визуализации и печати
IntField
DoubleField
StringField
TextField
RadioToggleField
ComboTogleField
ReferenceFieldкласс редактируемого и печатаемого int
класс редактируемого и печатаемого double
класс редактируемого и печатаемого string
класс редактируемого и печатаемого текстового окна (textarea)
класс выбора в диалоге с помощью RadioButton
класс выбора из списка в диалоге с помощью ComboBox
класс-ссылка - в диалоге редактируется с помощью drag&drop
IntField = {
string title = "" // название выводимое при печати и редактировании в диалоге
int value = 0 // значение поля
int min = 0 // контролируемое в диалоге мнимальное значение целого value
int max = 0 // контролируемое в диалоге максимальное значение целого value
int delta = 0 // шаг автоизменения при редактировании в диалоге
}
DoubleField = {
string title = "" // название выводимое при печати и редактировании в диалоге
double value = 0 // значение поля
double min = 0 // контролируемое в диалоге мнимальное значение вещественного value
double max = 0 // контролируемое в диалоге максимальное значение вещественного value
double coef = 0 // коэффициент перевода (для случая редактирования в одних единицах
// измерения, а использования в других)
double delta = 0 // шаг автоизменения при редактировании в диалоге
int decimals = 0 // количество выводимых при печати и редактировании знаков
// после десятичной точки
}
StringField = {
int max = 0 // максимальное количество символов в строке
string title = "" // название выводимое при печати и редактировании в диалоге
string value = "" // если строка ограничена одинарными кавычками '...' ограничитель строки
// (\0 не ставится)
}
TextField = { // аналогичен StringField, но представляет собой textarea (MLE)
int max = 0
string title = ""
string value = ""
}
RadioToggleField = { // название говорит само за себя - реализация диалогового элемента
string title = ""
int value = 0 // содержит номер выбранной строки
group elements = { } // набор строк типа string
}
ComboToggleField = { // аналогично предыдущему для объекта ComboBox
string title = ""
int value = 0
group elements = { }
}
ReferenceField = { // Объект-ссылка на другой объект. В диалоге реализует drag&drop
string title = ""
string class = "void" // имя класса объекты которого могут заполнять данное поля
// (их может быть много - class0|ckass1|...|classN)
string value = "" // имя полное файла содержащего объект
int value2 = 0 // WPS-objectid
}
Класс ReferenceField позволяет использовать множество настроек в одной,
если, например, программный продукт состоит из множества задач, каждая
из которых имеет свою настройку с индивидуальной и общей частью, и наоборот,
если настройки одного объекта используютсп во множестве других.
В качестве чисто практического (для описания изображения пород), реализован
класс масски заливки:
ColorMaskField = {
string title = ""
int fcolor = 0 // цвет переднего плана
int bcolor = 0 // цвет фона
string value = '' // маска заливки 8*8
}
Также можно заметить, что существуют и предустановленные данные:
На основе этих простых и сложных типов можно создавать свои классы скриптов
и их реализации использовать для настройки задач. Так, например, выглядит
класс-описание забойного двигателя:
DEngine drill={
string title="Забойный двигатель"
DoubleField diam_zd ={ title="Диаметр заб.двигателя" coef=0.001 decimals=1 delta=0.1 min=30 max=400}
DoubleField leng_zd ={ title="Длина заб.двигателя" coef=1 decimals=2 delta=0.01 max=50}
DoubleField q_zd ={ title="Расход заб.двигателя" coef=1000 decimals=1 delta=0.1 min=30 max=400}
DoubleField dens_zd ={ title="Плотность жидкости" coef=1000 decimals=2 delta=0.01 min=0.8 max=2.5}
DoubleField loss_px_zd ={ title="Потери давления на холостом ходу" coef=98100 min=1 max=200}
DoubleField freq_nx_zd ={ title="Частота вращения на холостом ходу" coef=0.01666 min=10 max=999}
DoubleField loss_pt_zd ={ title="Потери давления при торможении" coef=98100 min=1 max=200}
DoubleField moment_t_zd={ title="Тормозной момент" coef=10 min=1 max=1500}
ComboToggleField drop ={
string title ="Тип двигателя"
group elements = {
string = "турбобур с постоянной линией"
string = "турбобур с падающей линией"
string = "объемный двигатель"
}
}
}
А так выглядит описание конкретного забойного двигателя (/* комментарий
*/ в первой строке необходим для интерпретации текста как скрипта):
/* XObject */
DEngine = {
diam_zd = {value = 30.0000}
leng_zd = {value = 0.0000}
q_zd = {value = 30.0000}
dens_zd = {value = 0.8000}
loss_px_zd = {value = 1.0000}
freq_nx_zd = {value = 10.0000}
loss_pt_zd = {value = 1.0000}
moment_t_zd = {value = 1.0000}
drop = {value = 0}
}
Данный синтаксис показывает возможность изменения в каждом конкретном объекте
и вспомогательных полей: min, max и прочих.
Обратимся к заявленной самодиалоговости и документированности, т.е
визуализации и печати.
Создан, как говорилось ранее, WPS-объект интерпретирующий специально
организованные части описания классов как страницы своих свойств (property).
Для этого в описание класса вносятся специальные описания - group с
именами pages ( для диалога) и sections (для печати).
Вот как выглядит описывающая диалог часть класса :
group pages = {
namedgroup = { // описание страницы диалога
title = "%MajorTab" // символ % означает MajorTab
elements = {
string = "имя 1" // элемент класса
string = "имя 2"
...
string = "имя N"
}
}
namedgroup = { // вторая страница диалога
title = "MinorTab страницы"
elements = {
string = "имя 1"
string = "имя 2"
...
string = "имя N"
}
}
... // другие страницы диалога
}
Вот как выглядит (и что порождает) часть описания класса забойного двигателя
(DEngine) относящаяся к диалогу:
DEngine drill={
...
group pages = {
namedgroup = {
string title = "%Забойный двигатель"
group elements = {
string string = "drop"
string string = "diam_zd"
string string = "leng_zd"
string string = "q_zd"
string string = "dens_zd"
string string = "loss_px_zd"
string string = "freq_nx_zd"
string string = "loss_pt_zd"
string string = "moment_t_zd"
}
}
}
}
DEngine drill={
...
group sections={
namedgroup={
title = "Забойный двигатель"
elements={
string ="drop"
string ="diam_zd"
string ="leng_zd"
string ="q_zd"
string ="dens_zd"
string ="loss_px_zd"
string ="freq_nx_zd"
string ="loss_pt_zd"
string ="moment_t_zd"
}
}
}
}
Для использования предлагаемой системы работы с конфигурациями необходимо
в корне или каталоге указанном переменной окружения AMT_PATH создать файл
config.ini следующего содержания (на примере тренажера):
/* AMT */
group Config = {
string customer = "ЗАО АМТ"
int loglevel = 0 // -1 полный отладочный вывод в файл \xobject.log
// 0 выводить только сообщения обошибках
// 1 выводить предупреждения исообщения об ошибках
group classes = {
string = "classes.ini" // описание общих классов (обпзательно)
string = "common.ini" // описание другой группы общих классов
string = "DSTclass.ini" // описание классов бурового тренажера
string = "KRSclass.ini" // описание классов тренажера для капитального ремонта скважин
}
}
Этот файл используется WPS-объектом AMTWPSXObject реализующем диалоги и
печать объектов конфигурации.
Этот объект ассоциирован с расширением AMT. При программировании следует
самому прочитать описания используемых классов - например весь config.ini
и все перечисленные в нем файлы или только нужные задаче. Следующий текст
дает пример считывания всех библиотек классов описанных в config.ini:
#include "common.h"
XObject *o_main, *o_sub, *o_ref, *o_target, *o_config; // для сценария
// функция инициализирует подсистему предустановленных классов XObject
// и возвращает кол-во классов в библиотеках
int InitXClasses(void)
{
char buf[256]="", *str;
char *amtPath = getenv(AMT_PATH);
int num=0;
XOBJECT_INITIALIZE
// загрузка библиотек классов
buf[0] = 0;
if (amtPath) strcat(buf, amtPath);
strcat(buf, "\\");
strcat(buf, AMT_CONFIG);
o_config = XObject::loadObject(buf);
if (!o_config)
return 1;
o_config = o_config->getGroupElement("classes");
for (int i = 0; i
следующие строки дают представление о получении значений некоторых элементов
класса сценария для тренажера:
{
....
o_main=XObject::loadObject(file_name); // загрузка файла конкретного сценария
if (!o_main)
return 1; // ошибка чтения сценария - переход к тестовому режиму
...
o_sub = o_main->getGroupElement("title");
st->Scen->Name=strdup(o_sub->getStringValue());
o_sub = o_main->getGroupElement("model_type");
st->Scen->model_type=o_sub->getIntValue(); // тип (номер) модели данного тренажера
o_sub = o_main->getGroupElement2("mode|value"); // здесь используется составное имя
st->Scen->mode=o_sub->getIntValue(); // тип (номер) модели данного тренажера
...
// если элемент является ссылкой, можно воспользоваться методом GetValue(name),
// который сам и загрузит скрипт ссылки
o_ref=o_main->getValue("derrik") // загрузить описание буровой вышки
// вычислить значение параметра g_kv в системе СИ
st->Scen->g_kv=o_ref->getGroupElement2("g_kv|value")->getDoubleValue() *
o_ref->getGroupElement2("g_kv|coef")->getDoubleValue();
}
Как видно из примеров, доступ к элементам конфигурации (параметрам) может
производится по именам полей класса (name), по составным именам ("name|subname"),
а также по их порядковым номерам в group. В приведенном выше описании
забойного двигателя величина параметра
XObject *les, *student, *acc; // для студента
int InitStudentRecords(DST_STATE *st, int alarmtime, int alarmactive)
{
int res;
student=XObject::loadObject(st->StudentName); // загружаем файл объекта класса студент
if (!student)
return 1;
les=XObject::getInstance("Lesson", CLASS_NAMEDGROUP_NAME); // создаем объект класса занятие
acc=XObject::getInstance("Accid", CLASS_NAMEDGROUP_NAME); // создаем объект класса авария
// заполняем описание занятия
les->getGroupElement("sim")->setStringValue(st->SIM);
les->getGroupElement("mode")->setIntValue(st->Scen->mode);
les->getGroupElement("date")->setIntValue((int)(st->BeginTime));
les->getGroupElement("scen")->setStringValue(st->Scen->Name);
les->getGroupElement("ft")->setIntValue(alarmtime);
les->getGroupElement("hl")->setIntValue(st->TimeFromBeginReal);
if (alarmactive)
res=2;
else
res=1;
les->getGroupElement("res")->setIntValue(res);
return 0;
}
int AddAccidRecord(int num, int begintime, int howlong, int source)
{
acc->getGroupElement("num")->setIntValue(num);
acc->getGroupElement("bt")->setIntValue(begintime);
acc->getGroupElement("hl")->setIntValue(howlong);
acc->getGroupElement("src")->setIntValue(source);
les->getGroupElement("accids")->addGroupElement(acc->clone()); // клонирование класса
return 0;
}
int MakeStudentRecord(DST_STATE *st)
{
student->getGroupElement("lessons")->addGroupElement(les);
XObject::saveObject(st->StudentName,student);
delete acc;
delete student;
return 0;
}
void SaveAlarm(void)
{
AMessage *AMt;
if (!STATUS->StudentName)
return;
if (InitStudentRecords(STATUS, AlarmTime, AlarmActive))
return;
AMt=AccBeginList;
while (AMt)
{
AddAccidRecord(AMt->Anum, AMt->beginTime, AMt->howLong, AMt->source);
AMt=AMt->next;
}
MakeStudentRecord(STATUS);
}
Для заинтересовавшихся приведем описание public объектов класса XObject
class _Export XObject {
public:
static void setLogLevel(int);
static int log(char*, ...);
static int log(int, char*, ...);
static int logObject(XObject*, char* = NULL);
static void logClasses();
static void initClass();
static void unInitClass();
static int loadFile(char*, char**, int*);
static int saveFile(char*, char*, int);
static XObject* getInstance(char*, char*, int = FLAGS_NORMAL, int =-1);
static XObject* getInstanceText(char*, int*, int =-1);
static XObject* getInstanceBin(char*, int*, int = -1);
static int loadClasses(char*);
static XObject* loadObject(char*);
static int saveObject(char*, XObject*);
...
public:
static int getClassNum();
static char* getClassName(int);
...
public:
virtual ~XObject();
int isVoid();
int isInteger();
int isDouble();
int isString();
int isComplex();
int isGroup();
int isObject();
int isObject(char*);
int isBasic();
virtual XObject* clone();
int initFrom(XObject*);
char* getName();
int setName(char* n);
char* getClassName();
int getClassVersion();
int getFlags();
int setFlags(int);
void flagSet(int);
void flagClear(int);
int flagIsSet(int);
int getSizeBin();
int getSizeText();
int getIntValue();
int setIntValue(int);
double getDoubleValue();
int setDoubleValue(double);
char* getStringValue();
int getStringSize();
int getStringLength();
int setStringSize(int);
int setStringValue(int, char*);
int setStringValue(char*);
int getGroupSize();
XObject* getGroupElement(int);
XObject* getGroupElement(char*);
XObject* getGroupElement2(char*);
int addGroupElement(XObject*);
int removeGroupElement(int);
int removeGroup();
int loadBinFile(char*);
int readText(char*, int);
int readBin(char*, int);
int writeBin(char*, int);
int writeText(char*, int, int = 0);
virtual XObject* getValue();
virtual int show(int);
virtual int print(int);
// misc
static char* strPrintInteger(char*, int, int);
static char* strPrintDouble(char*, double, int, int);
static char* strPrintString(char*, char*, int, int*);
static char* strPrintStringEsc(char*, char*, int, int*);
...
};
Если вам понравилась описанная реализация идеи, вы можете скачать комплект
для разработчика здесь.
Если вы хотите поспорить или просто поговорить с автором,
пишите сюда.
Интересные ссылки:
Комментариев к странице: 0 | Добавить комментарий
Редактор: Дмитрий Бан
Оформление: Евгений Кулешов
(C) Russian Underground/2