Как часто нам приходится сталкиваться с обработкой текстовых потоков в реальном времени? Как минимум при каждой загрузке файлов инициализации или конфигурации и тому подобных параметрических данных. Хорошо, когда его содержимое сводится к формату «param = value» и можно воспользоваться стандартными инструментами нарезки. Но что если по ходу разработки программы возникла необходимость усложнить тексты до работы со ссылками? Или обрабатывать условия на этапе чтения? Более того реализовать ветвления? В такой ситуации обычно на скорую руку пишется парсер, занимающий первоначально некоторое количество строчек кода. Который однако со временем разрастается, начинает ветвиться и в конечном итоге приводит к самоповторению, либо заходит в самоисключающий тупик. Именно в этот момент и появляется в голове мысль, что вся суть смысловой разбивки текста сводится к определенному количеству шаблонных операций, зависимых от контекста. И все что требуется для обработки текстов любой сложности — это абстрактный обработчик шаблонов, а не сложносочиненный парсер с детальным описанием всех возникающих условий.
Предлагаю вниманию широкой общественности свободно настраиваемую реализацию процедуры деления текстовых потоков на отрезки и определения типа данных при помощи ветвящегося списка шаблонов. Суть работы заключается в сверке считываемых из строки символов с предварительно настроенными профилями и переключении связанных модификаторов. Посимвольно обработчик движется по строке, возвращая необходимую информацию при каждом разделении потока на отрезки. Весь процесс отнимает всего несколько действий на символ. Работа с шаблонами требует их предварительной ручной настройки в зависимости от сложности обрабатываемых текстов. В приведенной ниже реализации допускается разделение текста на слова и знаки разметки, разделение текстовых, числовых типов данных, выделение комментариев, цитат, функциональных операторов и команд для собственных интерпретаторов.
Шаблоны представляют собой группы данных, подразумевающих ассоциированный контекст и делятся на подгруппы, описывающие все принадлежащие элементы. Каждый элемент представляет собой конкретный символ и содержит до 5 модификаторов, влияющих на обработку: 1-код символа, 2-тип данных текущего символа, 3-модификатор изменения предыдущего типа данных, 4-изменение профиля, 5-условие изменения текущего типа данных, в зависимости от предыдущего.
Чего нет в этой реализации? Готовых решений. Суть предлагаемого метода не в полной автоматизации, а в свободном конструировании и постоянной динамике. Функционирование обработчика целиком и полностью зависит от предварительной настройки типовых шаблонов. Предлагаемый метод является вариацией на тему конструктора формальных систем и весь рабочий процесс сводится к оперированию абстрактными категориями, которые создает, настраивает и структурирует сам программист.
//глобальные структуры и параметры
INT64 splCutType = 0; //тип данных выделенного отрезка
INT64 splGlyphType = 0; //тип разрабатываемого знака
INT64 splLastType = 0; //тип предыдущего знака
INT64 splProfile = 0; //текущий загруженный шаблон
INT64 splitProfiles[7][256][6];
//типы данных
INT64 splTrash = 1;
INT64 splString = 2;
INT64 splDigits = 3;
INT64 splWords = 4;
INT64 splKey = 5;
INT64 splArgument = 6;
INT64 splEndofSub = 7;
//структура контекстных шаблонов
INT64 spCode = 1; //связанный char-code
INT64 spPrefix = 2; //изменение текущего типа данных
INT64 spPostfix = 3; //изменение предыдущего типа данных
INT64 spSwitch = 4; //переключение шаблона
INT64 spCntxCond = 5; //контекстные условия переключения шаблона
void splitSet(void) //настройка контекстных шаблонов
{
// 1.общий
//заполнение значений по умолчанию. остальные ячейки заполняются под номерами, соответствующими char-кодам символов.
for (int i = 1; i < 256; i++)
splitProfiles[1][i][2] = splWords;
splitProfiles[1][(unsigned char)('0')][spCode] = (unsigned char)('0');
splitProfiles[1][(unsigned char)('0')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('0')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('1')][spCode] = (unsigned char)('1');
splitProfiles[1][(unsigned char)('1')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('1')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('2')][spCode] = (unsigned char)('2');
splitProfiles[1][(unsigned char)('2')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('2')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('3')][spCode] = (unsigned char)('3');
splitProfiles[1][(unsigned char)('3')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('3')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('4')][spCode] = (unsigned char)('4');
splitProfiles[1][(unsigned char)('4')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('4')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('5')][spCode] = (unsigned char)('5');
splitProfiles[1][(unsigned char)('5')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('5')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('6')][spCode] = (unsigned char)('6');
splitProfiles[1][(unsigned char)('6')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('6')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('7')][spCode] = (unsigned char)('7');
splitProfiles[1][(unsigned char)('7')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('7')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('8')][spCode] = (unsigned char)('8');
splitProfiles[1][(unsigned char)('8')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('8')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('9')][spCode] = (unsigned char)('9');
splitProfiles[1][(unsigned char)('9')][spPrefix] = splDigits;
splitProfiles[1][(unsigned char)('9')][spCntxCond] = splTrash;
splitProfiles[1][(unsigned char)('\n')][spCode] = (unsigned char)('\n');
splitProfiles[1][(unsigned char)('\n')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)(' ')][spCode] = (unsigned char)(' '); //TAB
splitProfiles[1][(unsigned char)(' ')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)('/')][spCode] = (unsigned char)('/');
splitProfiles[1][(unsigned char)('/')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)('/')][spSwitch] = 2;
splitProfiles[1][(unsigned char)('[')][spCode] = (unsigned char)('[');
splitProfiles[1][(unsigned char)('[')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)('[')][spSwitch] = 3;
splitProfiles[1][(unsigned char)('"')][spCode] = (unsigned char)('"');
splitProfiles[1][(unsigned char)('"')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)('"')][spSwitch] = 4;
splitProfiles[1][(unsigned char)('$')][spCode] = (unsigned char)('$');
splitProfiles[1][(unsigned char)('$')][spPrefix] = splKey;
splitProfiles[1][(unsigned char)('$')][spSwitch] = 6;
splitProfiles[1][(unsigned char)(':')][spCode] = (unsigned char)(':');
splitProfiles[1][(unsigned char)(':')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)(',')][spCode] = (unsigned char)(',');
splitProfiles[1][(unsigned char)(',')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)('.')][spCode] = (unsigned char)('.');
splitProfiles[1][(unsigned char)('.')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)('=')][spCode] = (unsigned char)('=');
splitProfiles[1][(unsigned char)('=')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)(';')][spCode] = (unsigned char)(';');
splitProfiles[1][(unsigned char)(';')][spPrefix] = splTrash;
splitProfiles[1][(unsigned char)('+')][spCode] = (unsigned char)('+');
splitProfiles[1][(unsigned char)('+')][spPrefix] = splKey;
splitProfiles[1][(unsigned char)('&')][spCode] = (unsigned char)('&');
splitProfiles[1][(unsigned char)('&')][spPrefix] = splKey;
splitProfiles[1][(unsigned char)(' ')][spCode] = (unsigned char)(' '); //SPACE
splitProfiles[1][(unsigned char)(' ')][spPrefix] = splTrash;
// 2.комментарий
for (int i = 1; i < 256; i++)
splitProfiles[2][i][2] = splTrash;
splitProfiles[2][(unsigned char)('\n')][spCode] = (unsigned char)('\n');
splitProfiles[2][(unsigned char)('\n')][spPrefix] = splTrash;
splitProfiles[2][(unsigned char)('\n')][spSwitch] = 1;
splitProfiles[2][(unsigned char)('/')][spCode] = (unsigned char)('/');
splitProfiles[2][(unsigned char)('/')][spPrefix] = splTrash;
splitProfiles[2][(unsigned char)('/')][spSwitch] = 1;
// 3.массивный текст
for (int i = 1; i < 256; i++)
splitProfiles[3][i][2] = splString;
splitProfiles[3][(unsigned char)(']')][spCode] = (unsigned char)(']');
splitProfiles[3][(unsigned char)(']')][spPrefix] = splTrash;
splitProfiles[3][(unsigned char)(']')][spSwitch] = 1;
// 4.цитата
for (int i = 1; i < 256; i++)
splitProfiles[4][i][2] = splString;
splitProfiles[4][(unsigned char)('"')][spCode] = (unsigned char)('"');
splitProfiles[4][(unsigned char)('"')][spPrefix] = splTrash;
splitProfiles[4][(unsigned char)('"')][spSwitch] = 1;
// 5.оператор
for (int i = 1; i < 256; i++)
splitProfiles[5][i][2] = splKey;
splitProfiles[5][(unsigned char)(' ')][spCode] = (unsigned char)(' '); //SPACE
splitProfiles[5][(unsigned char)(' ')][spPrefix] = splTrash;
splitProfiles[5][(unsigned char)(' ')][spSwitch] = 1;
splitProfiles[5][(unsigned char)('\n')][spCode] = (unsigned char)('\n');
splitProfiles[5][(unsigned char)('\n')][spPrefix] = splTrash;
splitProfiles[5][(unsigned char)('\n')][spSwitch] = 1;
splitProfiles[5][(unsigned char)('(')][spCode] = (unsigned char)('(');
splitProfiles[5][(unsigned char)('(')][spPrefix] = splTrash;
splitProfiles[5][(unsigned char)('(')][spSwitch] = 6;
// 6.аргумент
for (int i = 1; i < 256; i++)
splitProfiles[6][i][2] = splArgument;
splitProfiles[6][(unsigned char)(')')][spCode] = (unsigned char)(')');
splitProfiles[6][(unsigned char)(')')][spPrefix] = splEndofSub;
splitProfiles[6][(unsigned char)(')')][spSwitch] = 1;
splitProfiles[6][(unsigned char)(',')][spCode] = (unsigned char)(',');
splitProfiles[6][(unsigned char)(',')][spPrefix] = splTrash;
splitProfiles[6][(unsigned char)(' ')][spCode] = (unsigned char)(' '); //SPACE
splitProfiles[6][(unsigned char)(' ')][spPrefix] = splTrash;
splitProfiles[6][(unsigned char)(' ')][spCntxCond] = splTrash;
}
INT64 split(INT64 *strPtr)
{
INT64 ret = 0;
//разбор длится до окончания строки, пока не встретится нулевой символ
while (*(char*)(*strPtr)) {
//при отсутствии дополнительных условий в пункте spCntxCond, тип данных меняется на новый
if (splitProfiles[splProfile][*(unsigned char*)(*strPtr)][spCntxCond] == 0 || splitProfiles[splProfile][*(unsigned char*)(*strPtr)][spCntxCond] == splGlyphType)
splGlyphType = splitProfiles[splProfile][*(unsigned char*)(*strPtr)][spPrefix];
//изменение предыдущего типа данных при возникновении ключевых символов в строке
if (splitProfiles[splProfile][*(unsigned char*)(*strPtr)][spPostfix] > 0)
splLastType = splitProfiles[splProfile][*(unsigned char*)(*strPtr)][spPostfix];
//изменение профиля
if (splitProfiles[splProfile][*(unsigned char*)(*strPtr)][spSwitch] > 0)
splProfile = splitProfiles[splProfile][*(unsigned char*)(*strPtr)][spSwitch];
//при смене типа данных происходит выход из функции для последующей нарезки
if (splGlyphType != splLastType) {
//тип данных сохраняется для доступа к нему вызывающей функции
splCutType = splLastType;
//предыдущий тип данных перестраивается на новый отрезок
splLastType = splGlyphType;
ret = 1;
goto finish;
}
//указатель увеличивается на единицу для движения по строке
*strPtr += 1;
}
//в конце строки последний отрезок сохраняет свой тип данных, действий для следующего отрезка не производится
splCutType = splGlyphType;
finish:
//при выходе из функции указатель переводится либо на начало следующего отрезка, либо на нулевой символ конца строки
*strPtr += 1;
return ret;
}
INT64 splitRet = 1;
char *text = "тест \"цитата\" 123 /комментарий/ [большой текст] $команда";
INT64 textPtr = (INT64)text;
INT64 lastPos = textPtr;
INT64 length = 0;
//номер профиля данных по умолчанию
splProfile = 1;
//тип данных устанавливается по первому символу
splGlyphType = splLastType = splitProfiles[1][*(unsigned char*)textPtr][spPrefix];
while (splitRet != 0) { //обработка завершится по окончанию строки
splitRet = split(&textPtr);
length = textPtr - lastPos - 1; //длина отрезка без крайнего символа (null-terminated, либо следующего отрезка)
if (splCutType != splTrash) { //если отрезок не содержит мусор, имеет смысл извлечь из него данные.
//далее копирование отрезка из строки в отдельную строку, либо запуск собственных интерпретаторов.
}
else {
//впрочем мусор также можно просмотреть или обработать
}
lastPos = textPtr - 1; //счетчик последнего прочитанного символа модифицируется
}
Что выдаст нам процедура на каждом шаге цикла while?
1. 'тест' — 4 символа — splWords
2. ' "' — 2 символа — splTrash
3. 'цитата' — 6 символов — splString
4. '" ' — 2 символа — splTrash
5. '123' — 3 символа — splDigit
6. ' /комментарий/ [' — 16 символов — splTrash
7. 'большой текст' — 13 символов — splString
8. '] ' — 2 символа — splTrash
9. '$команда' — 8 символов — splKey
Для того чтобы из текстовых файлов обращаться непосредственно к данным, будь то функции или публичные параметры, необходимо будет воспользоваться связующим инструментарием. Я воспользуюсь собственной реализацией примитивных векторных конструкций, позволяющей представить любые логические данные в виде векторного поля, размером от нуля до 256 элементов. Таким образом используя текстовые строки я буду вызывать связанные с ними данные.
INT64 baseVect[256];
INT64 vectData = 0; //связанная информация, которую несет в себе вектор.
INT64 vectUpSet = 1; //элемент надмножества, в поле которого лежит вектор.
INT64 vectField = 2; //следующий элемент в линейном ряду данного поля
INT64 vectSubSet = 3; //элемент подмножества, лежащий на поле данного вектора.
INT64 vectPrimitive(INT64 string, INT64 length) {
INT64 *vectPtr = baseVect[*(unsigned char*)string], *tempPtr = 0;
for (INT64 i = 1; i < length; i++) {
tempPtr = vectPtr[vectSubSet];
while ((INT64)tempPtr != 0) {
if (tempPtr[vectUpSet] == baseVect[*(unsigned char*)(string + i)])
break;
tempPtr = tempPtr[vectField];
}
if ((INT64)tempPtr == 0) {
tempPtr = (INT64)malloc(4 * 8);
tempPtr[vectUpSet] = baseVect[*(unsigned char*)(string + i)];
tempPtr[vectField] = vectPtr[vectSubSet];
tempPtr[vectSubSet] = 0;
vectPtr[vectSubSet] = (INT64)tempPtr;
}
vectPtr = (INT64)tempPtr;
}
return vectPtr;
}
//перед использованием обязательно требуется создание пула базовых векторов, представляющих все множество символов.
void vectConstruct()
{
for (int i = 0; i < 256; i++) {
baseVect[i] = (INT64)malloc(4 * 8);
((INT64*)baseVect[i])[vectSubSet] = 0;
}
}
INT64 incVal = 0; //публичный параметр для обработки из текстовой строки
INT64 inc(INT64 *ptr) { //функция, запускаемая из текстовой строки.
*ptr += 1;
return *ptr;
}
int main()
{
INT64 splitRet = 1;
//тестовая строка, содержащая команду запуска функции инкремента с указателем на публичную переменную.
char *text = "$inc(incVal)";
INT64 textPtr = (INT64)text;
INT64 lastPos = textPtr;
INT64 length = 0;
INT64(*subPtr)(INT64 Param, ...);
((INT64*)vectPrimitive("$inc", 4))[vectData] = &inc;//связывание текстовой строки и указателя на публичные данные
((INT64*)vectPrimitive("incVal", 6))[vectData] = &incVal;
//номер профиля данных по умолчанию
splProfile = 1;
//тип данных устанавливается по первому символу
splGlyphType = splLastType = splitProfiles[1][*(unsigned char*)textPtr][spPrefix];
while (splitRet != 0) { //обработка завершится по окончанию строки
splitRet = split(&textPtr);
length = textPtr - lastPos - 1; //длина выделенного отрезка без крайнего символа
if (splCutType != splTrash) { //если отрезок не содержит мусор, имеет смысл обработать данные.
switch (splCutType) {
case 5: { //splKey. блок сохранения указателя на функциональные данные.
subPtr = ((INT64*)vectPrimitive(lastPos, length))[vectData];
break;
}
case 6: { //splArgument. блок запуска функции с выделенным из строки аргументом.
(*subPtr)(((INT64*)vectPrimitive(lastPos, length))[vectData]);
break;
}
}
}
lastPos = textPtr - 1; //счетчик последнего прочитанного символа модифицируется
}
}
Что выдаст нам процедура на каждом шаге цикла while?
1. '$inc' — 4 символа — splKey
2. '(' — 1 символ — splTrash
3. 'incVal' — 6 символов — splArgument — ! на этом шаге произойдет запуск функции inc и значение incVal увеличится на 1
4. ')' — 1 символ — splTrash
INT64 srcVal = 0;
INT64 dstVal = 1;
INT64 recursPos = -1;
INT64 skipFlag = 1;
INT64 doWhile(INT64 *arg1, INT64 cond, INT64 *arg2) {
switch ((unsigned char)cond) {
case (unsigned char)('<'):
return (*arg1 < *arg2);
case (unsigned char)('>'):
return (*arg1 > *arg2);
case (unsigned char)('='):
return (*arg1 == *arg2);
default:
return 0;
}
}
Далее немного модифицируем цикл обработки лексем, введя в него некоторые новые категории:
int main()
{
INT64 splitRet = 1;
//командная строка содержит циклическую проверку неравенства и уже знакомую инкрементную функцию внутри цикла
char *text = "$doWhile(srcVal, <, dstVal) $inc(srcVal) $loop";
INT64 textPtr = (INT64)text;
INT64 lastPos = textPtr;
INT64 length = 0;
INT64(*subPtr)(INT64 Param, ...);
INT64 arguments[4];
INT64 argCounter = 0;
INT64 ret = 0;
((INT64*)vectPrimitive("$inc", 4))[vectData] = &inc; //все необходимые данные связываются векторами
((INT64*)vectPrimitive("$doWhile", 8))[vectData] = &doWhile;
((INT64*)vectPrimitive("$loop", 5))[vectData] = &recursPos;
((INT64*)vectPrimitive("srcVal", 6))[vectData] = &srcVal;
((INT64*)vectPrimitive("dstVal", 6))[vectData] = &dstVal;
((INT64*)vectPrimitive("<", 1))[vectData] = (unsigned char)('<');
((INT64*)vectPrimitive(">", 1))[vectData] = (unsigned char)('>');
((INT64*)vectPrimitive("=", 2))[vectData] = (unsigned char)('=');
read:
//номер профиля данных по умолчанию
splProfile = 1;
//тип данных устанавливается по первому символу
splGlyphType = splLastType = splitProfiles[1][*(unsigned char*)textPtr][spPrefix];
while (splitRet != 0) { //обработка завершится по окончанию строки
splitRet = split(&textPtr);
length = textPtr - lastPos - 1; //длина отрезка без крайнего символа (null-terminated, либо следующего отрезка)
if (splCutType != splTrash) { //если отрезок не содержит мусор, имеет смысл обработать данные.
//запрещащий skipFlag пропускает все нефункциональные отрезки в ожидании $loop
if (skipFlag == 0 && splCutType != splKey)
goto next;
switch (splCutType) {
case 5: { //splKey. блок сохранения функциональных указателей.
subPtr = ((INT64*)vectPrimitive(lastPos, length))[vectData];
//doWhile запоминает позицию для возможного возврата
if ((INT64)subPtr == &doWhile)
recursPos = lastPos;
else if ((INT64)subPtr == &recursPos && recursPos >= 0) {
//разрешающий skipFlag приводит к прыжку от позиции $loop к $doWhile
if (skipFlag > 0) {
splitRet = textPtr = lastPos = recursPos;
goto read;
}
//запрещающий skipFlag восстанавливает модификаторы рекурсии
recursPos = -1;
skipFlag = 1;
}
break;
}
case 6: { //splArgument. блок сохранения выделенных из строки аргументов.
argCounter++;
arguments[argCounter] = ((INT64*)vectPrimitive(lastPos, length))[vectData];
break;
}
case 7: { //splEndofSub. блок запуска функции с сохраненными аргументами.
switch (argCounter) {
case 1: ret = (*subPtr)(arguments[1]); break;
case 2: ret = (*subPtr)(arguments[1], arguments[2]); break;
case 3: ret = (*subPtr)(arguments[1], arguments[2], arguments[3]); break;
}
argCounter = 0;
if ((INT64)subPtr == &doWhile)
skipFlag = ret;
break;
}
}
}
next:
lastPos = textPtr - 1; //счетчик последнего прочитанного символа модифицируется
}
}
Что будет происходить на каждом шаге цикла while?
1. '$doWhile' — 8 символов — splKey — ! связанный указатель и текстовая позиция сохраняются
2. '(' — 1 символ — splTrash
3. 'srcVal' — 6 символов — splArgument — ! увеличивается счетчик аргументов, указатель сохраняется в массиве arguments.
4. ', ' — 2 символа — splTrash
5. '<' — 1 символ — splArgument — ! увеличивается счетчик аргументов, указатель сохраняется в массиве arguments.
6. ', ' — 2 символа — splTrash
7. 'dstVal' — 6 символов — splArgument — ! увеличивается счетчик аргументов, указатель сохраняется в массиве arguments.
8. ')' — 1 символ — splEndofSub — ! специальный тип данных инициализирует запуск функции, после чего счетчик аргументов сбрасывается. результат для функции doWhile сохраняется в специальном контейнере. отрицательный результат приводит к пропуску всех лексем до $loop.
9. ' ' — 1 символ — splTrash
10. '$inc' — 4 символа — splKey — ! связанный функциональный указатель сохраняется
11. '(' — 1 символ — splTrash
12. 'srcVal' — 6 символов — splArgument — ! увеличивается счетчик аргументов, указатель сохраняется в массиве arguments.
13. ')' — 1 символ — splEndofSub — ! спец.тип данных инициализирует запуск функции, счетчик аргументов сбрасывается.
14. ' ' — 1 символ — splTrash
15. '$loop' — 5 символов — splKey — ! если результат функции doWhile изначально положительный, поток откатывается на заранее сохраненную позицию. В противном случае чтение потока идет дальше.
Комментарии (10)
MichaelBorisov
19.03.2016 09:37Автор, а как ваш парсер соотносится с классическими решениями из теории, такими как регулярные выражения, конечные автоматы?
Ragel не пробовали? Очень интересный пакет, позволяет легко делать парсеры с генерацией компактного кода.Tatuin
19.03.2016 10:07Никак не соотносится. Это не конечный автомат, а абстрактный обработчик. Текстовый делитель, представляющий сплошной поток в виде сортированного потока, поделенного на типизированные отрезки. Что делать с содержащимися в таком потоке операторами — тема для отдельного абстрактного решения. Причем абстрактность подхода позволяет применять его не только на C. Изначально этот функционал был придуман на vbscript и использовался как предподготовка для самописного интерпретатора русского литературного текста. Ragel это несомненно интересно, но из другой категории.
bobermaniac
19.03.2016 12:29Мне кажется, вы написали какое-то подмножество LL(0)-лексера. Глючного, не поддерживающего рекурсию, но лексера.
Пора сделать второй шаг. Открыть любую статью по парсингу и прочитать про BNF и EBNF, LL и LR, SLR и LALR, PEG и Regex и познать дзен.Tatuin
19.03.2016 12:59+1Хм, а мне то казалось, что будущее интерпретации в векторных сетях. Пишу тут месяцами конструктор семантических полей и не знаю, что оказывается все тайны давно раскрыты и наилучшим вариантом являются парсеры прошлого века и стабильное существование в рамках англоязычного дзена. А творческие изыскания это очевидно от лукавого.
bobermaniac
19.03.2016 14:47А векторная сеть-то где? Пока я вижу парсер прошлого века, только многократно ухудшенный.
Tatuin
19.03.2016 23:15+1В статью добавлены дополнительные методы применения функционала, векторные связи, запуск внешних процедур, работа со ссылками и примеры рекурсии.
ps. Мне казалось у местных за привычку прокручивать пространные тексты и читать сразу исходный код, вникая так сказать в суть методики. Вот вы почему-то не видите в предложенном подходе возможности рекурсии, а она есть.
SBKarr
Делал похожий велосипед для своего проекта. Код там: https://github.com/SBKarr/stappler/blob/master/common/string/SPCharReader.h
Пример использования (разбор JSON) там: https://github.com/SBKarr/stappler/blob/master/common/data/SPDataValueJson.cpp (строки с 45 по 217). Скорость сей код давал примерно схожий с RapidJson (+- 5%, RapidJson быстрее разгребал массивы за счёт pool-allocator). Поддержка разбора как побайтово (CharReader<CharReaderType::Base>), так и посимвольно в UTF-8 (CharReader<CharReaderType::Utf8>.