текст обновлен 20.03.16 04:10

Как часто нам приходится сталкиваться с обработкой текстовых потоков в реальном времени? Как минимум при каждой загрузке файлов инициализации или конфигурации и тому подобных параметрических данных. Хорошо, когда его содержимое сводится к формату «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

Демонстрация рекурсии
Усложним задачу и попробуем реализовать рекурсию на основе потока отсортированных данных, возвращаемых процедурой split. Воспользуемся тем же векторным функционалом для связи с внешними процедурами и переменными, однако увеличим их количество:
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)


  1. SBKarr
    19.03.2016 09:22
    +1

    Делал похожий велосипед для своего проекта. Код там: 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>.


  1. MichaelBorisov
    19.03.2016 09:37

    Автор, а как ваш парсер соотносится с классическими решениями из теории, такими как регулярные выражения, конечные автоматы?

    Ragel не пробовали? Очень интересный пакет, позволяет легко делать парсеры с генерацией компактного кода.


    1. Tatuin
      19.03.2016 10:07

      Никак не соотносится. Это не конечный автомат, а абстрактный обработчик. Текстовый делитель, представляющий сплошной поток в виде сортированного потока, поделенного на типизированные отрезки. Что делать с содержащимися в таком потоке операторами — тема для отдельного абстрактного решения. Причем абстрактность подхода позволяет применять его не только на C. Изначально этот функционал был придуман на vbscript и использовался как предподготовка для самописного интерпретатора русского литературного текста. Ragel это несомненно интересно, но из другой категории.


      1. bobermaniac
        19.03.2016 12:29

        Мне кажется, вы написали какое-то подмножество LL(0)-лексера. Глючного, не поддерживающего рекурсию, но лексера.

        Пора сделать второй шаг. Открыть любую статью по парсингу и прочитать про BNF и EBNF, LL и LR, SLR и LALR, PEG и Regex и познать дзен.


        1. Tatuin
          19.03.2016 12:59
          +1

          Хм, а мне то казалось, что будущее интерпретации в векторных сетях. Пишу тут месяцами конструктор семантических полей и не знаю, что оказывается все тайны давно раскрыты и наилучшим вариантом являются парсеры прошлого века и стабильное существование в рамках англоязычного дзена. А творческие изыскания это очевидно от лукавого.


          1. bobermaniac
            19.03.2016 14:47

            А векторная сеть-то где? Пока я вижу парсер прошлого века, только многократно ухудшенный.


            1. Tatuin
              19.03.2016 23:15
              +1

              В статью добавлены дополнительные методы применения функционала, векторные связи, запуск внешних процедур, работа со ссылками и примеры рекурсии.
              ps. Мне казалось у местных за привычку прокручивать пространные тексты и читать сразу исходный код, вникая так сказать в суть методики. Вот вы почему-то не видите в предложенном подходе возможности рекурсии, а она есть.


  1. bugrazoid
    21.03.2016 16:51

    А на GitHub будет ссылка?


    1. Tatuin
      21.03.2016 17:08

      Может я не совсем понимаю о чем речь. Намекните.