Привет всем!
Несколько лет назад меня начал беспокоить вопрос создания статических (создаваемых и изменяемых до процесса компиляции) перечислений. Перечислений я хотел не простых, которые реализованы в С/С++, а с набором дополнительных возможностей, в том числе и ассоциированными столбцами данных произвольного типа, своего рода статическая база данных с доступом по уникальному идентификатору.
Тогда в моем понимании ясно выделились три типа объектов, которые могут быть уникальным идентификатором: числовой индекс, числовой идентификатор, символьный идентификатор. При попытке применить каждый из них для решения задачи перехода от ключа к значению сформировались основные их недостатки и преимущества:
Основная задача проекта EnumGenerator — сгенерировать перечисление, которое удобно, безопасно и эффективно объединяет эти идентификаторы в единую конструкцию. Чтобы к одному значению можно было обратиться тремя способами:
Входные данные EnumGenerator, перечисление Color3 в таблице Excel:
Выходные данные EnumGenerator:
Пример использования перечисления Color3 в тестовом проекте Qt:
Как попробовать EnumGenerator?
Как использовать EnumGenerator в своем проекте?
Схема прохождения данных при использовании EnumGenerator:
Вот пример, который наглядно демонстрирует основные возможности использования генератора, файл Excel.
Рассмотрим часть этого файла более подробно:
Структура входного файла:
Надеюсь, это выглядит наглядным, простым и логичным.
Буду рад отзывам и советам по улучшению, спасибо!
Связанные ссылки:
String enum — строковые enum
Ещё одна реализация Enums для Python
Несколько лет назад меня начал беспокоить вопрос создания статических (создаваемых и изменяемых до процесса компиляции) перечислений. Перечислений я хотел не простых, которые реализованы в С/С++, а с набором дополнительных возможностей, в том числе и ассоциированными столбцами данных произвольного типа, своего рода статическая база данных с доступом по уникальному идентификатору.
Тогда в моем понимании ясно выделились три типа объектов, которые могут быть уникальным идентификатором: числовой индекс, числовой идентификатор, символьный идентификатор. При попытке применить каждый из них для решения задачи перехода от ключа к значению сформировались основные их недостатки и преимущества:
- Числовой индекс — это уникальное целочисленное значение, элемент последовательного массива, для С/С++ это диапазон [0;n), где n — размер массива. Если мы видим индекс 5, то это подразумевает, что обязательно есть и индексы [0;4]. Примеры: индекс классического С-массива, хеш-таблица, адрес ячейки физической памяти. Краткий вывод: скорость обработки максимальная, сопровождаемость минимальная.
- Числовой идентификатор — это уникальное целочисленное значение, которое лишено обязанности быть последовательным. Примеры: дескриптор произвольного (файл, сокет, устройство) объекта, идентификатор потока, адрес какой-то переменной или функции в процессе. Краткий вывод: скорость обработки высокая, сопровождаемость средняя.
- Символьный идентификатор — это уникальное строковое значение, которое, в отличие от чисел, само по себе наделено некоторым логическим смыслом. Примеры: препроцессорное определение с помощью #define, классическое перечисление с помощью enum, название переменной в программе, ключ в объекте json, значение в формате XML. Краткий вывод: скорость обработки минимальная, сопровождаемость максимальная.
Основная задача проекта EnumGenerator — сгенерировать перечисление, которое удобно, безопасно и эффективно объединяет эти идентификаторы в единую конструкцию. Чтобы к одному значению можно было обратиться тремя способами:
- Очень быстро по числовому индексу. Для основного использования в тексте программы. Пример: обычное перечисление.
- Быстро и гибко по числовому идентификатору. Для сохранения значения в энергонезависимое хранилища и безопасное восстановление из него. Для обмена данными с другими программами, которые могут иметь более старую или более новую версию этого же перечисления. Пример: база данных, протокол сетевого взаимодействия.
- Удобно и наглядно по символьному идентификатору. Для сохранения значения в конфигурационный файлы, который может редактироваться человеком, и безопасное восстановление из него. Пример: файл конфигурации *.ini, *.json, *.yaml,*.xml.
Входные данные EnumGenerator, перечисление Color3 в таблице Excel:
Выходные данные EnumGenerator:
Перечисление Color3 в файле 'Enums.hpp'
class Color3
{
public:
enum Value
{
Black = 0, //! men in black
Blue = 1, //! blue ocean
Green = 2, //! green forest
Invalid = 3,
Red = 4, //! lady in red
White = 5 //! white snow
};
static const int ValueCount = 6;
static const Value ValueInvalid = Invalid;
Color3(Value val = ValueInvalid) : m_ValueCur(val) {}
Color3(const Color3 &other) : m_ValueCur(other.m_ValueCur) {}
explicit Color3(int val) : m_ValueCur(ValueInvalid)
{
int index = NS_JDSoft::NS_EnumGenerator::ValueFindLinear(IDInteger, ValueCount, val);
if (index >= 0) m_ValueCur = Value(index);
}
explicit Color3(const char * val) : m_ValueCur(ValueInvalid)
{
int index = NS_JDSoft::NS_EnumGenerator::StringFindBinary(StringValues, ValueCount, val);
if (index >= 0) m_ValueCur = Value(index);
}
Color3 &operator =(Value val) { m_ValueCur = val; return *this; }
Color3 &operator =(const Color3 &other) { m_ValueCur = other.m_ValueCur; return *this; }
bool operator ==(Value val) const { return m_ValueCur == val; }
bool operator ==(const Color3 &other) const { return m_ValueCur == other.m_ValueCur; }
bool isValid() const { return m_ValueCur != ValueInvalid; }
Value toValue() const { return m_ValueCur; }
int toInt() const { return IDInteger[m_ValueCur]; }
const char * toString() const { return StringValues[m_ValueCur]; }
static const char * enumName() { return "Color3"; }
private:
static const char * const StringValues[ValueCount];
static const int IDInteger[ValueCount];
Value m_ValueCur;
};
Перечисление Color3 в файле 'Enums.cpp'
const char * const Color3::StringValues[Color3::ValueCount]=
{
"Black",
"Blue",
"Green",
"Invalid",
"Red",
"White"
};
const int Color3::IDInteger[Color3::ValueCount] =
{
0,
255,
65280,
-1,
16711680,
16777215
};
Пример использования перечисления Color3 в тестовом проекте Qt:
#include "Enums.hpp"
...
// Создание объекта перечисления и проверка невалидности его значения
Color3 colorPen;
QCOMPARE(colorPen.isValid(), false); // isValid()
QVERIFY(colorPen == Color3::Invalid); // operator ==(Value val)
// Проверка правильности основных преобразований
QCOMPARE(colorPen.toValue(), Color3::Invalid); // toValue()
QCOMPARE(colorPen.toInt(), -1); // toInt()
QCOMPARE(colorPen.toString(), "Invalid"); // toString()
// Задание валидного значения
colorPen = Color3::Red; // operator =(Value val)
QCOMPARE(colorPen.isValid(), true);
// Проверка правильности основных преобразований
QCOMPARE(colorPen.toValue(), Color3::Red);
QCOMPARE(colorPen.toInt(), 0xFF0000);
QCOMPARE(colorPen.toString(), "Red");
// Создание объекта с известным значением
QCOMPARE(Color3(Color3::Green).toString(), "Green");
QCOMPARE(Color3(0x00FF00).toString(), "Green");
QCOMPARE(Color3("Green").toString(), "Green");
// Сравнение объектов
QVERIFY(Color3(0x0000FF) == Color3("Blue")); // operator ==(const Color3 &other)
Как попробовать EnumGenerator?
- Скачать проект на свой компьютер, Download zip и распаковать его.
- Запустить файл Enums.lua в интерпретаторе lua.
Самый простой способ для пользователей windows:
1) Скачать минимальную версию интерпретатора lua и распаковать его.
2) Щелкнуть правой кнопкой мыши по файлу 'Enums.lua', кликнуть «Открыть» или «Открыть с помощью» и выбрать распакованный файл 'lua.exe'.
- Убедиться, что в директории сгенерировались файлы 'Enums.hpp' и 'Enums.cpp'. Готово! :)
Как использовать EnumGenerator в своем проекте?
- Попробовать EnumGenerator как описано в предыдущем пункте.
- Переместить директорию с файлом 'EnumGenerator.lua' в стабильное место.
Путь к директории с файлом 'EnumGenerator.lua' будем называть 'ENUMGENERATOR_PATH'. - Открыть файл 'Enums.lua' и отредактировать его так, чтобы переменная EnumGenerator содержала полный путь к генератору.
Для этого и последующих проектов это нужно сделать один раз.
- Скопировать файлы 'Enums.xls' и 'Enums.lua' в директорию своего проекта.
- Отредактировать файл 'Enums.xls' в Excel или OpenOffice для своих перечислений.
- Сохранить отредактированный файл 'Enums.xls' как есть, дополнительно сохранить его в формате csv в файл 'Enums.csv' и закрыть редактор.
- Запустить файл Enums.lua из директории своего проекта в интерпретаторе lua и убедиться, что генерация прошла успешно:
- При запуске в консольном режиме скрипт пишет «Successfully completed!».
- При запуске из другой программы код возврата интерпретатора равен 0.
Схема прохождения данных при использовании EnumGenerator:
Вот пример, который наглядно демонстрирует основные возможности использования генератора, файл Excel.
Рассмотрим часть этого файла более подробно:
Структура входного файла:
- Файл состоит из независимых частей-блоков, [0;∞]. Каждая часть (Part) начинается словом «PartBegin» и заканчивается словом «PartEnd» в самой левой ячейке. После «PartBegin» в этой же ячейке в круглых скобках указываются обязательные и опциональные параметры этой части, пример: PartBegin(Type=Enum, Name=Country). Все строки между «PartBegin» и «PartEnd» являются телом части (PartData). Последовательность частей учитывается.
- Тело части (PartData) состоит из 0-го столбца (самый левый), который определяет тип всей строки и последующих ячеек [0;∞]. Сейчас реализовано 3 типа: Header — заглавие данных, Comment — комментарий, ""(пустая строка) — содержит основные данные в соответствии с заглавием.
- Заглавие данных (Header) описывает обязательный идентификатор столбца и возможные дополнительные параметры.
Надеюсь, это выглядит наглядным, простым и логичным.
Буду рад отзывам и советам по улучшению, спасибо!
Связанные ссылки:
String enum — строковые enum
Ещё одна реализация Enums для Python