Разработка синтаксического анализатора языка запросов на языке 1С
1 Введение |
1.1 Благодарности |
1.2 О чем пойдет речь? |
1.3 Что надо знать, перед тем как приступить к чтению? |
2 Необязательные инструменты использованные в процессе разработки |
3 Введение |
3.1 Примитивный синтаксический анализатор |
3.2 Разработка модели |
3.3 Комбинации парсеров |
3.3.1 Последовательность: e1 e2 |
3.3.2 Упорядоченный выбор: e1 / e2 |
3.3.3 Один или более (e+), нуль или более (e*), необязательно (e?) |
3.3.4 И-предикат (&e), НЕ-предикат (!e) |
3.3.5 Упростим код |
4 Расширение возможностей парсеров |
4.1 Регистро-независимое сравнение символа |
4.2 Сравнение со строкой |
4.3 Регистро-независимое сравнение со строкой |
4.4 Интервал |
4.5 Сравнение с кодом символа |
4.6 Преобразование результата |
|
1 Введение
1.1 Благодарности
Эта публикация никогда не увидела бы свет, если бы не поддержка моей жены - Людмилы. Мой ласковый ангел, эту статью я посвящаю тебе и твоему терпению.
1.2 О чем пойдет речь?
В этой статье будет рассмотрен один из вариантов реализации генератора синтаксических анализаторов. Предложенный вариант один из многих, его не следует рассматривать как единственно возможный, а только как источник идей для разработки собственных анализаторов. Основной целью разработки будет получить парсер для разбора текста запроса, но в учебных целях будет также разработано несколько анализаторов для других простых языков
1.3 Что надо знать, перед тем как приступить к чтению?
Для понимания статьи достаточно знать язык и среду 1С на среднем уровне. Единственную сложность может вызвать использование операторов Выполнить() и Вычислить() .Если использование этих конструкций вызывает у вас сложности, обратитесь к официальной документации
2 Необязательные инструменты использованные в процессе разработки
При разработке кода будут использоваться следующие инструменты:
1. Система юнит-тестирования xUnitFor1C. Так как задача разбора текста достаточно хорошо формализуется, то для упрощения разработки будет использована достаточно развитая система xUnitFor1C. Внимание, эта статья - не инструкция по применению подсистемы юнит-тестирования, поэтому все вопросы касательно функционирования и возможностей использования, пожалуйста, адресуйте разработчикам. Согласно методологии TDD сперва пишется тест, затем код. В этой статье при разработке модели будет применен другой подход – тест является информацией как использовать тот или иной метод. Классический TDD будет продемонстрирован при разработке учебных анализаторов(в следующей публикации).
2. Снегопат. Думаю, не нуждается в рекламе, пользуясь случаем, хочу сказать Александру огромное спасибо за возможность скачивать бесплатные версии.
3. Статья написана с помощью системы генерации текста Scribble (входит в состав DrRacket, скачать можно здесь - http://racket-lang.org/download/, исходный код статьи - здесь)
3 Введение
3.1 Примитивный синтаксический анализатор
Классификация парсеров и типы алгоритмов поверхностно описаны в вики Синтаксический анализ В этой статье буду описывать процесс построения парсера по алгоритму нисходящего разбора, как это описано в статье PEG После того как будут описаны все необходимые примитивы, будет разработана система генерации парсеров на основании простого текстового представления
Парсер, с точки зрения PEG это - функция, принимающая на вход строку и возвращающая либо успех, либо неудача. Самый простой способ задать определения успеха/неудачи следующий -
1. Для успешного разбора - функция возвращает обработанные данные (это может быть как и прочатнный символ/символы, так и произвольные данные), а также остаток строки, получившися после работы парсера. Код выглядит так -
Функция РазобраноУспешно(Значение, Остаток)
СтруктураВозврата = Новый Структура;
СтруктураВозврата.Вставить("Тип",1);
СтруктураВозврата.Вставить("Значение",Значение);
СтруктураВозврата.Вставить("Остаток",Остаток);
Возврат СтруктураВозврата;
Конецфункции
2. В случае если функция не смогла разобрать входную строку, то возвращается структура с типом ошибки и неизмененной строкой
Функция РазобраноНеудачно(Остаток)
СтруктураВозврата = Новый Структура;
СтруктураВозврата.Вставить("Тип",0);
СтруктураВозврата.Вставить("Остаток",Остаток);
Возврат СтруктураВозврата;
Конецфункции
Тогда определение функции разбора одного символа можно описать так
Функция ПарсерОдногоСимвола(Образец,СтрокаАнализа)
СимволАнализа = Сред(СтрокаАнализа,1,1);
Если СимволАнализа = Образец Тогда
Возврат РазобраноУспешно(СимволАнализа,Сред(СтрокаАнализа,2));
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Конецфункции
Для проверки работоспособности кода определим 2 теста
Перем юТест;
Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт
юТест = ЮнитТестирование;
ВсеТесты = Новый Массив;
ВсеТесты.Добавить("Тест_ОдинСимволУспешно");
ВсеТесты.Добавить("Тест_ОдинСимволНеудачно");
Возврат ВсеТесты;
Конецфункции
Процедура Тест_АтомарныйУспешно() Экспорт
образец = Новый структура("Тип,Значение,Остаток",1,"м","ама");
результат = ПарсерОдногоСимвола("м","мама");
юТест.ПроверитьРавенство(результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(результат.Значение, образец.Значение);
юТест.ПроверитьРавенство(результат.Остаток, образец.Остаток);
Конецпроцедуры
Процедура Тест_АтомарныйНеудачно() Экспорт
образец = Новый структура("Тип,Остаток",0,"мама");
результат = ПарсерОдногоСимвола("М","мама");
юТест.ПроверитьРавенство(результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(результат.Остаток, образец.Остаток);
Конецпроцедуры
Если вы используете Снегопат, то запуск тестов можно произвести прямо в конфигураторе (с помощью скрипта xddTestRunner.js), иначе вам необходимо запустить 1С в режиме предприятия, и с помощью обработки xddTestRunner.epf проверить выполнение тестов. Определенные нами 2 теста отработают вполне корректно и результат тестирования будет положительным.
3.2 Разработка модели
Как следует из документации, последовательность парсеров - это комбинация 2 парсеров, работающая следующим образом -
- 1. Получить результат работы первого парсера
- 2. Если результат работы - неудача, то парсер завершает работу и возвращает неудача
- 3. Проверяется результат работы второго парсера
- 4. Если результат работы - неудача, то парсер завершает работу и возвращает неудача
- 5. Оба парсера завершены успешно, возвращается истина
Попытаемся реализовать это описание в виде функции
Функция ПоследовательностьПарсеров(Первый,Второй,СтрокаАнализа)
РезультатРаботыПарсера = вызвать первый парсер;
Если Неудача(Первый) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
РезультатРаботыПарсера = вызвать второй парсер;
Если Неудача(Первый) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Возврат РазобраноУспешно(Что вернуть?,Как получить остаток строки?);
Конецфункции
Простая на вид функция поставила перед нами столько вопросов. Будем решать их последовательно. И первое к чему приступим - проектирование структуры нашего парсера.
Для начала - определимся с режимами, в которых будет существовать наш парсер. В проектируемой модели будет 2 режима - режим компиляции и режим выполнения . Во время компиляции мы будем строить парсер, во время выполнения - разбирать полученную строку с помощью парсера
В нашей реализации парсер будет представляться структурой с обязательным полем "Тип" и списком аргументов которые будут применяться для каждого типа парсера Возможная реализация функции ПостроитьПарсер приведена ниже
Функция ПостроитьПарсер(Тип,Аргумент1 = Неопределено, Аргумент2 = Неопределено)
Если Тип = "match" Тогда
Возврат Новый Структура("Тип,Образец",Тип,Аргумент1);
Иначе
Вызватьисключение "Неизвестный тип построения парсера """+Тип+"""";
Конецесли;
Конецфункции
А функция применения парсера может тогда выглядит так
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
Если Парсер.Тип = "match" Тогда
Возврат ПарсерОдногоСимвола(Парсер.Образец,СтрокаАнализа);
Иначе
Вызватьисключение "Неизвестный тип для приеменения парсера """+Парсер.Тип+"""";
Конецесли;
Конецфункции
Тест для проверки и документирования варианта вызова -
Процедура Тест_АтомарныйУдачноНоваяМодель() Экспорт
Парсер = ПостроитьПарсер("match","м");
Результат = ПрименитьПарсер(Парсер,"мама");
Образец = РазобраноУспешно("м","ама");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Результат.Остаток, образец.Остаток);
юТест.ПроверитьРавенство(Результат.Значение, образец.Значение);
Конецпроцедуры
Теперь наша функция для построения последовательности, выглядит так
Функция ПоследовательностьПарсеров(Первый,Второй,СтрокаАнализа)
РезультатРаботыПарсера1 = ПрименитьПарсер(Первый,СтрокаАнализа);
Если Неудача(РезультатРаботыПарсера1) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
РезультатРаботыПарсера2 = ПрименитьПарсер(Второй,Как получить остаток строки после первого парсера?);
Если Неудача(РезультатРаботыПарсера2) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Возврат РазобраноУспешно(Что вернуть?,Как получить остаток строки?);
Конецфункции
В коде используется неопределенная функция Неудача() . Она, как и прочие сервисные функции, для обработки результата парсера описаны ниже.
Функция кнстНеудача()
Возврат 0;
Конецфункции
Функция кнстУспех()
Возврат 1;
Конецфункции
Функция РазобраноУспешно(Значение,Остаток)
СтруктураВозврата = Новый Структура;
СтруктураВозврата.Вставить("Тип",кнстУспех());
СтруктураВозврата.Вставить("Значение",Значение);
СтруктураВозврата.Вставить("Остаток",Остаток);
Возврат СтруктураВозврата;
Конецфункции
Функция РазобраноНеудачно(Остаток)
СтруктураВозврата = Новый Структура;
СтруктураВозврата.Вставить("Тип",кнстНеудача());
СтруктураВозврата.Вставить("Остаток",Остаток);
Возврат СтруктураВозврата;
Конецфункции
Функция Успех(Значение)
Возврат
ТипЗнч(Значение) = Тип("Структура")
И Значение.Свойство("Тип")
И Значение.Тип = кнстУспех();
Конецфункции
Функция Неудача(Значение)
Возврат
ТипЗнч(Значение) = Тип("Структура")
И Значение.Свойство("Тип")
И Значение.Тип = кнстНеудача();
Конецфункции
Функция Остаток(Значение)
Если Не (Успех(Значение)
Или Неудача(Значение)) Тогда
Вызватьисключение "Неверное определение структуры парсера"
Конецесли;
Возврат Значение.Остаток;
Конецфункции
Функция Значение(Значение)
Если Не (Успех(Значение)
И Значение.Свойство("Значение")) Тогда
Вызватьисключение "Неверное определение структуры парсера"
Конецесли;
Возврат Значение.Значение;
Конецфункции
С учетом введенных функций, наша функция комбинации последовательности принимает вид
Функция ПоследовательностьПарсеров(Первый,Второй,СтрокаАнализа)
РезультатРаботыПарсера1 = ПрименитьПарсер(Первый,СтрокаАнализа);
Если Неудача(РезультатРаботыПарсера1) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
РезультатРаботыПарсера2 = ПрименитьПарсер(Второй,Остаток(РезультатРаботыПарсера1));
Если Неудача(РезультатРаботыПарсера2) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Возврат РазобраноУспешно(Что вернуть?,Остаток(РезультатРаботыПарсера2));
Конецфункции
Осталось определиться, что будет возвращать эта функция. Нам необходимо вернуть оба результата работы парсеров, и самым простым способом будет вернуть массив. В итоге функция принимает вид
Функция ПоследовательностьПарсеров(Первый,Второй,СтрокаАнализа)
РезультатРаботыПарсера1 = ПрименитьПарсер(Первый,СтрокаАнализа);
Если Неудача(РезультатРаботыПарсера1) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
РезультатРаботыПарсера2 = ПрименитьПарсер(Второй,Остаток(РезультатРаботыПарсера1));
Если Неудача(РезультатРаботыПарсера2) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
МассивВозврата = Новый Массив;
МассивВозврата.Добавить(Значение(РезультатРаботыПарсера1));
МассивВозврата.Добавить(Значение(РезультатРаботыПарсера2));
Возврат РазобраноУспешно(МассивВозврата,Остаток(РезультатРаботыПарсера2));
Конецфункции
Напишем простой тест для проверки тестовой грамматики
|
‹ТестоваяГрамматика› |
::= |
‹м› ‹а› |
|
‹м› |
::= |
м |
|
‹а› |
::= |
а |
Процедура Тест_Последовательно() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
Результат = ПоследовательностьПарсеров(БукваМ,БукваА,"мама");
МассивРезультата = Новый Массив;
МассивРезультата.Добавить("м");
МассивРезультата.Добавить("а");
Образец = РазобраноУспешно(МассивРезультата,"ма");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[1], Значение(образец)[1]);
Конецпроцедуры
Если вы что-то недопоняли -пройдитесь по коду с отладчиком, потому как этот подход будет дальше использован, будет развиваться и усложняться. Другими словами - код приведенный выше - самый сложный момент на всю публикацию. Его надо понять, что бы дальнейшие рассуждения не стали "китайской грамотой".
3.3 Комбинации парсеров
3.3.1 Последовательность: e1 e2
В предыдущем параграфе мы разработали функцию парсера последовательности из 2 парсеров. Но 2 парсера в последовательности - не самый частый случай в практике, чаще последовательность содержит куда больше элементов.
Изменим представление для хранения списка парсеров в последовательности. Для этого используем массив
Функция ПоследовательностьПарсеров(ПоследовательностьПарсеров,СтрокаАнализа)
МассивВозврата = Новый Массив;
СтрокаРаботы = СтрокаАнализа;
Для Каждого Парсер Из ПоследовательностьПарсеров Цикл
РезультатРаботы = ПрименитьПарсер(Парсер,СтрокаРаботы);
Если Неудача(РезультатРаботы) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
МассивВозврата.Добавить(Значение(РезультатРаботы));
СтрокаРаботы = Остаток(РезультатРаботы);
Конеццикла;
Возврат РазобраноУспешно(МассивВозврата,СтрокаРаботы);
Конецфункции
Функция ПостроитьПарсер(Тип,Аргумент1 = Неопределено, Аргумент2 = Неопределено)
Если Тип = "match" Тогда
Возврат Новый Структура("Тип,Образец",Тип,Аргумент1);
Иначеесли Тип = "seq" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначе
Вызватьисключение "Неизвестный тип построения парсера """+Тип+"""";
Конецесли;
Конецфункции
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
Если Парсер.Тип = "match" Тогда
Возврат ПарсерОдногоСимвола(Парсер.Образец,СтрокаАнализа);
Иначеесли Парсер.Тип = "seq" Тогда
Возврат ПоследовательностьПарсеров(Парсер.Последовательность,СтрокаАнализа);
Иначе
Вызватьисключение "Неизвестный тип для приеменения парсера """+Парсер.Тип+"""";
Конецесли;
Конецфункции
Проверим работоспособность парсера последовательности на следующей грамматике
|
‹тест1› |
::= |
‹м› ‹а› ‹м› ‹а› |
|
‹м› |
::= |
м |
|
‹а› |
::= |
а |
Процедура Тест_Последовательно() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
МассивПарсеров = Новый Массив;
МассивПарсеров.Добавить(БукваМ);
МассивПарсеров.Добавить(БукваА);
МассивПарсеров.Добавить(БукваМ);
МассивПарсеров.Добавить(БукваА);
ТестовыйПарсер = ПостроитьПарсер("seq",МассивПарсеров);
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = Новый Массив;
МассивРезультата.Добавить("м");
МассивРезультата.Добавить("а");
МассивРезультата.Добавить("м");
МассивРезультата.Добавить("а");
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[1], Значение(образец)[1]);
юТест.ПроверитьРавенство(Значение(Результат)[2], Значение(образец)[2]);
юТест.ПроверитьРавенство(Значение(Результат)[3], Значение(образец)[3]);
Конецпроцедуры
Как и ожидалось - тест отрабатывает успешно, но чисто с эстетической точки зрения последовательность кода
МассивПарсеров = Новый Массив;
МассивПарсеров.Добавить(БукваМ);
МассивПарсеров.Добавить(БукваА);
МассивПарсеров.Добавить(БукваМ);
МассивПарсеров.Добавить(БукваА);
выглядит некрасиво. Давайте преобразуем эти строки в вызов функции ВМассив(БукваМ,БукваА,БукваМ,БукваА)
Функция ДобавитьВМассив(МассивДобавления,Элемент)
Если Элемент = Неопределено Тогда
Возврат МассивДобавления;
Конецесли;
МассивДобавления.Добавить(Элемент);
Конецфункции
Функция ВМассив(Элемент1=Неопределено,Элемент2=Неопределено,Элемент3=Неопределено,Элемент4=Неопределено,Элемент5=Неопределено,Элемент6=Неопределено)
// Согласен, что выглядит некрасиво, но 1С - не lisp, приходится терпеть вот такую ерунду.
МассивВозврата = Новый Массив;
Для х = 1 По 6 Цикл
ДобавитьВМассив(МассивВозврата,Вычислить("Элемент"+х))
Конеццикла;
Возврат МассивВозврата;
Конецфункции
Если однажды нам не хватить аргументов в функции ВМассив, мы всегда можем расширить список, добавив необходимое число аргументов в объявление функции. После такого изменения тест можно переписать, например, так
Процедура Тест_Последовательно() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
МассивПарсеров = Новый Массив;
МассивПарсеров.Добавить(БукваМ);
МассивПарсеров.Добавить(БукваА);
МассивПарсеров.Добавить(БукваМ);
МассивПарсеров.Добавить(БукваА);
ТестовыйПарсер = ПостроитьПарсер("seq",МассивПарсеров);
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = Новый Массив;
МассивРезультата.Добавить("м");
МассивРезультата.Добавить("а");
МассивРезультата.Добавить("м");
МассивРезультата.Добавить("а");
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[1], Значение(образец)[1]);
юТест.ПроверитьРавенство(Значение(Результат)[2], Значение(образец)[2]);
юТест.ПроверитьРавенство(Значение(Результат)[3], Значение(образец)[3]);
Конецпроцедуры
3.3.2 Упорядоченный выбор: e1 / e2
Рассмотрим определение для операции выбора - Оператор выбора e1 / e2 сначала вызывает e1 и, если e1 успешно, возвращает её результат. Иначе, если e1проваливается, оператор выбора восстанавливает входную строку в состояние, предшествующее вызову e1, и вызывает e2, возвращая её результат.
Код парсера для 2 аргументов выглядит так
Функция ОперацияВыбор(Первый,Второй,СтрокаАнализа)
РезультатРаботыПарсера1 = ПрименитьПарсер(Первый,СтрокаАнализа);
Если Успех(РезультатРаботыПарсера1) Тогда
Возврат РезультатРаботыПарсера1;
Конецесли;
Возврат ПрименитьПарсер(Второй,СтрокаАнализа);
Конецфункции
Расширим определение оператора выбора таким образом - Оператор выбора последовательно применяет входящие в его состав альтернативы до тех пор, пока не встретиться парсер вернувший Успех() или не будут просмотрены все альтернативы. Возвращает значение первого успешного парсера и завершает свою работу или значение вычисления последней альтернативы
В нашем коде реализовано это будет так
Функция ОперацияВыбор(ПоследовательностьПарсеров,СтрокаАнализа)
Для Каждого Парсер Из ПоследовательностьПарсеров Цикл
РезультатРаботы = ПрименитьПарсер(Парсер,СтрокаАнализа);
Если Успех(РезультатРаботы) Тогда
Возврат РезультатРаботы
Конецесли;
Конеццикла;
Возврат РезультатРаботы;
Конецфункции
Добавим определение оператора выбора в функции ПостроитьПарсер() иПрименитьПарсер
Функция ПостроитьПарсер(Тип,Аргумент1 = Неопределено, Аргумент2 = Неопределено)
Если Тип = "match" Тогда
Возврат Новый Структура("Тип,Образец",Тип,Аргумент1);
Иначеесли Тип = "seq" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "/" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначе
Вызватьисключение "Неизвестный тип построения парсера """+Тип+"""";
Конецесли;
Конецфункции
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
Если Парсер.Тип = "match" Тогда
Возврат ПарсерОдногоСимвола(Парсер.Образец,СтрокаАнализа);
Иначеесли Парсер.Тип = "seq" Тогда
Возврат ПоследовательностьПарсеров(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "/" Тогда
Возврат ОперацияВыбор(Парсер.Последовательность,СтрокаАнализа);
Иначе
Вызватьисключение "Неизвестный тип для приеменения парсера """+Парсер.Тип+"""";
Конецесли;
Конецфункции
Традиционно закончим работу с новым оператором простым тестом для грамматики
|
‹тест1› |
::= |
‹М/П› ‹А› ‹М/П› ‹А› |
|
‹М/П› |
::= |
м | п |
|
‹А› |
::= |
а |
Процедура Тест_ОперацияВыбор() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваП = ПостроитьПарсер("match","п");
БукваА = ПостроитьПарсер("match","а");
БукваМилиП = ПостроитьПарсер("/",ВМассив(БукваМ,БукваП));
ТестовыйПарсер = ПостроитьПарсер("seq",ВМассив(БукваМилиП,БукваА,БукваМилиП,БукваА));
Результат = ПрименитьПарсер(ТестовыйПарсер,"папа");
МассивРезультата = ВМассив("п","а","п","а");
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[1], Значение(образец)[1]);
юТест.ПроверитьРавенство(Значение(Результат)[2], Значение(образец)[2]);
юТест.ПроверитьРавенство(Значение(Результат)[3], Значение(образец)[3]);
Конецпроцедуры
Запустим его, удостоверимся что все работает и перейдем к следующему этапу.
3.3.3 Один или более (e+), нуль или более (e*), необязательно (e?)
Как и ранее, возьмем определения для операторов с wiki - Операторы нуль-или-более, один-или-более и необязательности поглощают соответственно нуль или более, одно или более, или нуль либо одно последовательное появление своего подвыражения e. В отличие от КС-грамматик и регулярных выражений, эти операторы всегда являются жадными, и поглощают столько входных экземпляров, сколько могут.
И переведем каждый из этик операторов в код (как видите в написании парсеров нет ничего сложного - читай постановку задачи и пиши код)
Парсер "Один или более" можно определить так
Функция ОдинИлиБолее(Парсер,СтрокаАнализа)
СтрокаРаботы = СтрокаАнализа;
МассивРезультата = Новый Массив;
Пока Истина Цикл
Результат = ПрименитьПарсер(Парсер,СтрокаРаботы);
Если Неудача(Результат) Тогда
Прервать;
Конецесли;
МассивРезультата.Добавить(Значение(Результат));
СтрокаРаботы = Остаток(Результат);
Конеццикла;
Если МассивРезультата.Количество() = 0 Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Иначе
Возврат РазобраноУспешно(МассивРезультата,СтрокаРаботы);
Конецесли;
Конецфункции
Изменим функции ПостроитьПарсер и ПрименитьПарсер
Функция ПостроитьПарсер(Тип,Аргумент1 = Неопределено, Аргумент2 = Неопределено)
Если Тип = "match" Тогда
Возврат Новый Структура("Тип,Образец",Тип,Аргумент1);
Иначеесли Тип = "seq" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "/" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "+" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначе
Вызватьисключение "Неизвестный тип построения парсера """+Тип+"""";
Конецесли;
Конецфункции
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
Если Парсер.Тип = "match" Тогда
Возврат ПарсерОдногоСимвола(Парсер.Образец,СтрокаАнализа);
Иначеесли Парсер.Тип = "seq" Тогда
Возврат ПоследовательностьПарсеров(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "/" Тогда
Возврат ОперацияВыбор(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "+" Тогда
Возврат ОдинИлиБолее(Парсер.Парсер,СтрокаАнализа);
Иначе
Вызватьисключение "Неизвестный тип для приеменения парсера """+Парсер.Тип+"""";
Конецесли;
Конецфункции
Напишем тест. Кстати, обратите внимание, оператор "+" возвращает массив с результатами работы вложенного парсера.
Процедура Тест_ОдинИлиБолее_Один() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("+",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"ма");
МассивРезультата = ВМассив(ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
Конецпроцедуры
Процедура Тест_ОдинИлиБолее_Более() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваП = ПостроитьПарсер("match","п");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("+",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив(ВМассив("м","а"),ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
юТест.ПроверитьРавенство(Значение(Результат)[1][0], Значение(образец)[1][0]);
юТест.ПроверитьРавенство(Значение(Результат)[1][1], Значение(образец)[1][1]);
Конецпроцедуры
Процедура Тест_ОдинИлиБолее_Ноль() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("+",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"папа");
Образец = РазобраноНеудачно("папа");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
Конецпроцедуры
Парсер "Ноль или более" определяется по аналогии с парсером "Один или более", но всегда возвращает успешный результат разбора
Функция НольИлиБолее(Парсер,СтрокаАнализа)
СтрокаРаботы = СтрокаАнализа;
МассивРезультата = Новый Массив;
Пока Истина Цикл
Результат = ПрименитьПарсер(Парсер,СтрокаРаботы);
Если Неудача(Результат) Тогда
Прервать;
Конецесли;
МассивРезультата.Добавить(Значение(Результат));
СтрокаРаботы = Остаток(Результат);
Конеццикла;
Возврат РазобраноУспешно(МассивРезультата,СтрокаРаботы);
Конецфункции
Изменим функции ПостроитьПарсер и ПрименитьПарсер, что бы добавить поддержку парсера "Ноль и более"
Функция ПостроитьПарсер(Тип,Аргумент1 = Неопределено, Аргумент2 = Неопределено)
Если Тип = "match" Тогда
Возврат Новый Структура("Тип,Образец",Тип,Аргумент1);
Иначеесли Тип = "seq" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "/" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "+" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначеесли Тип = "*" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначе
Вызватьисключение "Неизвестный тип построения парсера """+Тип+"""";
Конецесли;
Конецфункции
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
Если Парсер.Тип = "match" Тогда
Возврат ПарсерОдногоСимвола(Парсер.Образец,СтрокаАнализа);
Иначеесли Парсер.Тип = "seq" Тогда
Возврат ПоследовательностьПарсеров(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "/" Тогда
Возврат ОперацияВыбор(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "+" Тогда
Возврат ОдинИлиБолее(Парсер.Парсер,СтрокаАнализа);
Иначеесли Парсер.Тип = "*" Тогда
Возврат НольИлиБолее(Парсер.Парсер,СтрокаАнализа);
Иначе
Вызватьисключение "Неизвестный тип для приеменения парсера """+Парсер.Тип+"""";
Конецесли;
Конецфункции
Традиционные тесты для проверки
Процедура Тест_НольИлиБолее_Ноль() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("*",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"папа");
МассивРезультата = Новый Массив;
Образец = РазобраноУспешно(МассивРезультата,"папа");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат).Количество(), Значение(образец).Количество());
Конецпроцедуры
Процедура Тест_НольИлиБолее_Более() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("*",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив(ВМассив("м","а"),ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
юТест.ПроверитьРавенство(Значение(Результат)[1][0], Значение(образец)[1][0]);
юТест.ПроверитьРавенство(Значение(Результат)[1][1], Значение(образец)[1][1]);
Конецпроцедуры
Остался последний простой оператор - Необязательно. Определим его таким образом
Функция Необязательно(Парсер,СтрокаАнализа)
Результат = ПрименитьПарсер(Парсер,СтрокаАнализа);
Если Успех(Результат) Тогда
Возврат Результат;
Иначе
Возврат РазобраноУспешно(Неопределено,СтрокаАнализа);
Конецесли;
Конецфункции
Функция ПостроитьПарсер(Тип,Аргумент1 = Неопределено, Аргумент2 = Неопределено)
Если Тип = "match" Тогда
Возврат Новый Структура("Тип,Образец",Тип,Аргумент1);
Иначеесли Тип = "seq" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "/" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "+" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначеесли Тип = "*" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначеесли Тип = "?" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначе
Вызватьисключение "Неизвестный тип построения парсера """+Тип+"""";
Конецесли;
Конецфункции
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
Если Парсер.Тип = "match" Тогда
Возврат ПарсерОдногоСимвола(Парсер.Образец,СтрокаАнализа);
Иначеесли Парсер.Тип = "seq" Тогда
Возврат ПоследовательностьПарсеров(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "/" Тогда
Возврат ОперацияВыбор(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "+" Тогда
Возврат ОдинИлиБолее(Парсер.Парсер,СтрокаАнализа);
Иначеесли Парсер.Тип = "*" Тогда
Возврат НольИлиБолее(Парсер.Парсер,СтрокаАнализа);
Иначеесли Парсер.Тип = "?" Тогда
Возврат Необязательно(Парсер.Парсер,СтрокаАнализа);
Иначе
Вызватьисключение "Неизвестный тип для приеменения парсера """+Парсер.Тип+"""";
Конецесли;
Конецфункции
Конечно же, тесты. Без них – никак!
Процедура Тест_Необязательно_Есть() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("?",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив("м","а");
Образец = РазобраноУспешно(МассивРезультата,"ма");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
Конецпроцедуры
Процедура Тест_Необязательно_Нет() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("?",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"папа");
Образец = РазобраноУспешно(Неопределено,"папа");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат), Значение(образец));
Конецпроцедуры
3.3.4 И-предикат (&e), НЕ-предикат (!e)
Что бы не изобретать велосипед и не страдать приступами альтернативного мышления, определения для операторов "&" и "!" возьмем с wiki - Выражение &e вызывает подвыражение e, и возвращает успех, если e успешно, и провал в противном случае, но никогда не поглощает ввода. Аналогично, выражение !e срабатывает успешно, если e проваливается и проваливается, если e успешно, так же не поглощая ввода.
Сложностей в определении этих операторов нет.
Функция ИПредикат(Парсер,СтрокаАнализа)
Результат = ПрименитьПарсер(Парсер,СтрокаАнализа);
Если Успех(Результат) Тогда
Возврат РазобраноУспешно(Неопределено,СтрокаАнализа);
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Конецфункции
Обратите внимание - в функцию РазобраноУспешно(...) передается входная строка, а не остаток от применения парсера.
Аналогично определяем и Не-предикат
Функция НеПредикат(Парсер,СтрокаАнализа)
Результат = ПрименитьПарсер(Парсер,СтрокаАнализа);
Если Неудача(Результат) Тогда
Возврат РазобраноУспешно(Неопределено,СтрокаАнализа);
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Конецфункции
Добавим операторы "!" и "&" в функции ПостроитьПарсер() иПрименитьПарсер()
Функция ПостроитьПарсер(Тип,Аргумент1 = Неопределено, Аргумент2 = Неопределено)
Если Тип = "match" Тогда
Возврат Новый Структура("Тип,Образец",Тип,Аргумент1);
Иначеесли Тип = "seq" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "/" Тогда
Возврат Новый Структура("Тип,Последовательность",Тип,Аргумент1);
Иначеесли Тип = "+" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначеесли Тип = "*" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначеесли Тип = "?" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначеесли Тип = "!" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначеесли Тип = "&" Тогда
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент1);
Иначе
Вызватьисключение "Неизвестный тип построения парсера """+Тип+"""";
Конецесли;
Конецфункции
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
Если Парсер.Тип = "match" Тогда
Возврат ПарсерОдногоСимвола(Парсер.Образец,СтрокаАнализа);
Иначеесли Парсер.Тип = "seq" Тогда
Возврат ПоследовательностьПарсеров(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "/" Тогда
Возврат ОперацияВыбор(Парсер.Последовательность,СтрокаАнализа);
Иначеесли Парсер.Тип = "+" Тогда
Возврат ОдинИлиБолее(Парсер.Парсер,СтрокаАнализа);
Иначеесли Парсер.Тип = "*" Тогда
Возврат НольИлиБолее(Парсер.Парсер,СтрокаАнализа);
Иначеесли Парсер.Тип = "?" Тогда
Возврат Необязательно(Парсер.Парсер,СтрокаАнализа);
Иначеесли Парсер.Тип = "!" Тогда
Возврат НеПредикат(Парсер.Парсер,СтрокаАнализа);
Иначеесли Парсер.Тип = "&" Тогда
Возврат ИПредикат(Парсер.Парсер,СтрокаАнализа);
Иначе
Вызватьисключение "Неизвестный тип для приеменения парсера """+Парсер.Тип+"""";
Конецесли;
Конецфункции
И сразу тесты для проверки работы.
Процедура Тест_ИПредикат() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("seq",ВМассив(БукваМиА,ПостроитьПарсер("&",БукваМ)));
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив(ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"ма");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
Конецпроцедуры
Процедура Тест_НеПредикат() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваП = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("seq",ВМассив(БукваМиА,ПостроитьПарсер("!",БукваП)));
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив(ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"ма");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
Конецпроцедуры
3.3.5 Упростим код
Давайте посмотрим на определение функции ПостроитьПарсер(... иПрименитьПарсер(...) и подумаем - как она будет расти при добавлении новых операторов? Правильный ответ - быстро. Добавление нового оператора будет приводить к добавлению как минимум 2 строк кода и уже при 15 операторах наша функция будет не влезать в экран, работать с ней станет сложно
Откажемся от второго аргумента в функции ПостроитьПарсер(...) и применим паттерн "Pluggable Selector" к функции ПрименитьПарсер(...) . Наш код преобразуется в такой
Перем СписокФункций;
Функция ЗарегистрироватьФункцииПарсера(ТипПарсера,ФункцияРазбора)
СписокФункций[ТипПарсера] = ФункцияРазбора
Конецфункции
Функция ПостроитьПарсер(Тип,Аргумент = Неопределено)
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент);
Конецфункции
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
ФункцияПарсера = СписокФункций [Парсер.Тип];
Если ФункцияПарсера = Неопределено Тогда
Вызватьисключение "Неизвестный тип парсера "+Парсер.Тип;
Конецесли;
Возврат Вычислить(ФункцияПарсера + "(Парсер,СтрокаАнализа)");
Конецфункции
Процедура Инициализация()
СписокФункций = Новый Соответствие;
ЗарегистрироватьФункцииПарсера ("match", "ПарсерОдногоСимвола");
ЗарегистрироватьФункцииПарсера ("seq", "ПоследовательностьПарсеров");
ЗарегистрироватьФункцииПарсера ("/", "ОперацияВыбор");
ЗарегистрироватьФункцииПарсера ("+", "ОдинИлиБолее");
ЗарегистрироватьФункцииПарсера ("*", "НольИлиБолее");
ЗарегистрироватьФункцииПарсера ("?", "Необязательно");
ЗарегистрироватьФункцииПарсера ("!", "НеПредикат");
ЗарегистрироватьФункцииПарсера ("&", "ИПредикат");
Конецпроцедуры
Учитывая что мы поменяли интерфейс вызова функций (раньше передавались аргументы, а теперь они завернуты в структуру и храняться по ключу "Парсер", тесты написаные для старой версии интерпретатора парсеров все закончатся неудачно. Но менять их не будем. Лучше изменим код под тесты. Новый вариант интерпретатора выглядит так
Перем юТест;
Перем СписокФункций;
Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт
юТест = ЮнитТестирование;
ВсеТесты = Новый Массив;
ВсеТесты.Добавить("Тест_Последовательно");
ВсеТесты.Добавить("Тест_ОперацияВыбор");
ВсеТесты.Добавить("Тест_ОдинИлиБолее_Один");
ВсеТесты.Добавить("Тест_ОдинИлиБолее_Более");
ВсеТесты.Добавить("Тест_ОдинИлиБолее_Ноль");
ВсеТесты.Добавить("Тест_НольИлиБолее_Ноль");
ВсеТесты.Добавить("Тест_НольИлиБолее_Более");
ВсеТесты.Добавить("Тест_Необязательно_Есть");
ВсеТесты.Добавить("Тест_Необязательно_Нет");
ВсеТесты.Добавить("Тест_ИПредикат");
ВсеТесты.Добавить("Тест_НеПредикат");
Возврат ВсеТесты;
КонецФункции
Функция ДобавитьВМассив(МассивДобавления,Элемент)
Если Элемент = Неопределено Тогда
Возврат МассивДобавления;
Конецесли;
МассивДобавления.Добавить(Элемент);
Конецфункции
Функция ВМассив(Элемент1=Неопределено,Элемент2=Неопределено,Элемент3=Неопределено,Элемент4=Неопределено,Элемент5=Неопределено,Элемент6=Неопределено)
//Выглядит некрасиво, согласен, но 1С - не lisp, приходится терпеть вот такую ерунду.
МассивВозврата = Новый Массив;
Для х = 1 По 6 Цикл
ДобавитьВМассив(МассивВозврата,Вычислить("Элемент"+х))
Конеццикла;
Возврат МассивВозврата;
Конецфункции
Функция кнстНеудача()
Возврат 0;
Конецфункции
Функция кнстУспех()
Возврат 1;
Конецфункции
Функция РазобраноУспешно(Значение,Остаток)
Возврат Новый Структура("Тип,Значение,Остаток",кнстУспех(),Значение,Остаток);
Конецфункции
Функция РазобраноНеудачно(Остаток)
Возврат Новый Структура("Тип,Остаток",кнстНеудача(),Остаток);
Конецфункции
Функция Успех(Значение)
Возврат ТипЗнч(Значение) = Тип("Структура") И Значение.Свойство("Тип") И Значение.Тип = кнстУспех();
Конецфункции
Функция Неудача(Значение)
Возврат ТипЗнч(Значение) = Тип("Структура") И Значение.Свойство("Тип") И Значение.Тип = кнстНеудача();
Конецфункции
Функция Остаток(Значение)
Если Не (Успех(Значение) Или Неудача(Значение)) Тогда
Вызватьисключение "Неверное определение структуры парсера"
Конецесли;
Возврат Значение.Остаток;
Конецфункции
Функция Значение(Значение)
Если Не (Успех(Значение) И Значение.Свойство("Значение")) Тогда
Вызватьисключение "Неверное определение структуры парсера"
Конецесли;
Возврат Значение.Значение;
Конецфункции
Функция ПарсерОдногоСимвола(Парсер,СтрокаАнализа)
СимволАнализа = Сред(СтрокаАнализа,1,1);
Если СимволАнализа = Парсер.Парсер Тогда
Возврат РазобраноУспешно(СимволАнализа,Сред(СтрокаАнализа,2));
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Конецфункции
Функция ПоследовательностьПарсеров(ПоследовательностьПарсеров,СтрокаАнализа)
МассивВозврата = Новый Массив;
СтрокаРаботы = СтрокаАнализа;
Для Каждого Парсер Из ПоследовательностьПарсеров.Парсер Цикл
РезультатРаботы = ПрименитьПарсер(Парсер,СтрокаРаботы);
Если Неудача(РезультатРаботы) Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
МассивВозврата.Добавить(Значение(РезультатРаботы));
СтрокаРаботы = Остаток(РезультатРаботы);
Конеццикла;
Возврат РазобраноУспешно(МассивВозврата,СтрокаРаботы);
Конецфункции
Функция ОперацияВыбор(ПоследовательностьПарсеров,СтрокаАнализа)
Для Каждого Парсер Из ПоследовательностьПарсеров.Парсер Цикл
РезультатРаботы = ПрименитьПарсер(Парсер,СтрокаАнализа);
Если Успех(РезультатРаботы) Тогда
Возврат РезультатРаботы
Конецесли;
Конеццикла;
Возврат РезультатРаботы;
Конецфункции
Функция ОдинИлиБолее(Парсер,СтрокаАнализа)
СтрокаРаботы = СтрокаАнализа;
МассивРезультата = Новый Массив;
РабочийПарсер = Парсер.Парсер;
Пока Истина Цикл
Результат = ПрименитьПарсер(РабочийПарсер,СтрокаРаботы);
Если Неудача(Результат) тогда
Прервать;
КонецЕсли;
МассивРезультата.Добавить(Значение(Результат));
СтрокаРаботы = Остаток(Результат);
КонецЦикла;
Если МассивРезультата.Количество() = 0 Тогда
Возврат РазобраноНеудачно(СтрокаАнализа);
Иначе
Возврат РазобраноУспешно(МассивРезультата,СтрокаРаботы);
КонецЕсли;
КонецФункции
Функция НольИлиБолее(Парсер,СтрокаАнализа)
СтрокаРаботы = СтрокаАнализа;
МассивРезультата = Новый Массив;
РабочийПарсер = Парсер.Парсер;
Пока Истина Цикл
Результат = ПрименитьПарсер(РабочийПарсер,СтрокаРаботы);
Если Неудача(Результат) тогда
Прервать;
КонецЕсли;
МассивРезультата.Добавить(Значение(Результат));
СтрокаРаботы = Остаток(Результат);
КонецЦикла;
Возврат РазобраноУспешно(МассивРезультата,СтрокаРаботы);
КонецФункции
Функция Необязательно(Парсер,СтрокаАнализа)
Результат = ПрименитьПарсер(Парсер.Парсер,СтрокаАнализа);
Если Успех(Результат) Тогда
Возврат Результат;
Иначе
Возврат РазобраноУспешно(Неопределено,СтрокаАнализа);
Конецесли;
Конецфункции
Функция ИПредикат(Парсер,СтрокаАнализа)
Результат = ПрименитьПарсер(Парсер.Парсер,СтрокаАнализа);
Если Успех(Результат) Тогда
Возврат РазобраноУспешно(Неопределено,СтрокаАнализа);
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Конецфункции
Функция НеПредикат(Парсер,СтрокаАнализа)
Результат = ПрименитьПарсер(Парсер.Парсер,СтрокаАнализа);
Если Неудача(Результат) Тогда
Возврат РазобраноУспешно(Неопределено,СтрокаАнализа);
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Конецфункции
Функция ЗарегистрироватьФункцииПарсера(ТипПарсера,ФункцияРазбора)
СписокФункций[ТипПарсера] = ФункцияРазбора
Конецфункции
Функция ПостроитьПарсер(Тип,Аргумент = Неопределено)
Возврат Новый Структура("Тип,Парсер",Тип,Аргумент);
Конецфункции
Функция ПрименитьПарсер(Парсер, СтрокаАнализа)
ФункцияПарсера = СписокФункций [Парсер.Тип];
Если ФункцияПарсера = Неопределено Тогда
Вызватьисключение "Неизвестный тип парсера "+Парсер.Тип;
Конецесли;
Возврат Вычислить(ФункцияПарсера + "(Парсер,СтрокаАнализа)");
Конецфункции
Процедура Инициализация()
СписокФункций = Новый Соответствие;
ЗарегистрироватьФункцииПарсера ("match", "ПарсерОдногоСимвола");
ЗарегистрироватьФункцииПарсера ("seq", "ПоследовательностьПарсеров");
ЗарегистрироватьФункцииПарсера ("/", "ОперацияВыбор");
ЗарегистрироватьФункцииПарсера ("+", "ОдинИлиБолее");
ЗарегистрироватьФункцииПарсера ("*", "НольИлиБолее");
ЗарегистрироватьФункцииПарсера ("?", "Необязательно");
ЗарегистрироватьФункцииПарсера ("!", "НеПредикат");
ЗарегистрироватьФункцииПарсера ("&", "ИПредикат");
Конецпроцедуры
Процедура Тест_Последовательно() экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
ТестовыйПарсер = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА,БукваМ,БукваА));
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив("м","а","м","а");
Образец = РазобраноУспешно(МассивРезультата,"");
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[1], Значение(образец)[1]);
юТест.ПроверитьРавенство(Значение(Результат)[2], Значение(образец)[2]);
юТест.ПроверитьРавенство(Значение(Результат)[3], Значение(образец)[3]);
Конецпроцедуры
Процедура Тест_ОперацияВыбор() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваП = ПостроитьПарсер("match","п");
БукваА = ПостроитьПарсер("match","а");
БукваМилиП = ПостроитьПарсер("/",ВМассив(БукваМ,БукваП));
ТестовыйПарсер = ПостроитьПарсер("seq",ВМассив(БукваМилиП,БукваА,БукваМилиП,БукваА));
Результат = ПрименитьПарсер(ТестовыйПарсер,"папа");
МассивРезультата = ВМассив("п","а","п","а");
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[1], Значение(образец)[1]);
юТест.ПроверитьРавенство(Значение(Результат)[2], Значение(образец)[2]);
юТест.ПроверитьРавенство(Значение(Результат)[3], Значение(образец)[3]);
Конецпроцедуры
Процедура Тест_ОдинИлиБолее_Один() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("+",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"ма");
МассивРезультата = ВМассив(ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
Конецпроцедуры
Процедура Тест_ОдинИлиБолее_Более() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваП = ПостроитьПарсер("match","п");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("+",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив(ВМассив("м","а"),ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
юТест.ПроверитьРавенство(Значение(Результат)[1][0], Значение(образец)[1][0]);
юТест.ПроверитьРавенство(Значение(Результат)[1][1], Значение(образец)[1][1]);
Конецпроцедуры
Процедура Тест_ОдинИлиБолее_Ноль() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("+",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"папа");
Образец = РазобраноНеудачно("папа");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
Конецпроцедуры
Процедура Тест_НольИлиБолее_Ноль() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("*",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"папа");
МассивРезультата = новый Массив;
Образец = РазобраноУспешно(МассивРезультата,"папа");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат).Количество(), Значение(образец).Количество());
Конецпроцедуры
Процедура Тест_НольИлиБолее_Более() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("*",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив(ВМассив("м","а"),ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
юТест.ПроверитьРавенство(Значение(Результат)[1][0], Значение(образец)[1][0]);
юТест.ПроверитьРавенство(Значение(Результат)[1][1], Значение(образец)[1][1]);
Конецпроцедуры
Процедура Тест_Необязательно_Есть() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("?",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив("м","а");
Образец = РазобраноУспешно(МассивРезультата,"ма");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
Конецпроцедуры
Процедура Тест_Необязательно_Нет() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("?",БукваМиА);
Результат = ПрименитьПарсер(ТестовыйПарсер,"папа");
Образец = РазобраноУспешно(Неопределено,"папа");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат), Значение(образец));
Конецпроцедуры
Процедура Тест_ИПредикат() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("seq",ВМассив(БукваМиА,ПостроитьПарсер("&",БукваМ)));
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив(ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"ма");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
Конецпроцедуры
Процедура Тест_НеПредикат() Экспорт
БукваМ = ПостроитьПарсер("match","м");
БукваА = ПостроитьПарсер("match","а");
БукваП = ПостроитьПарсер("match","а");
БукваМиА = ПостроитьПарсер("seq",ВМассив(БукваМ,БукваА));
ТестовыйПарсер = ПостроитьПарсер("seq",ВМассив(БукваМиА,ПостроитьПарсер("!",БукваП)));
Результат = ПрименитьПарсер(ТестовыйПарсер,"мама");
МассивРезультата = ВМассив(ВМассив("м","а"));
Образец = РазобраноУспешно(МассивРезультата,"ма");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0][0], Значение(образец)[0][0]);
юТест.ПроверитьРавенство(Значение(Результат)[0][1], Значение(образец)[0][1]);
Конецпроцедуры
Инициализация();
Так как скачивание файла стоит денег, то привожу код модуля полностью.
Запускаем тесты и видим что все в порядке. TDD - вполне приемлемый стиль разработки, как вы видите. Даже в 1С.
В принципе, этого набора парсеров нам достаточно, что бы реализовать парсер для любой грамматики, которая может быть описана PEG-парсером. Но прежде чем приступить к этому, давайте научимся расширять возможности парсеров.
4 Расширение возможностей парсеров
Страшным словом "расширение" на самом деле называется процесс разработки функции разбора и добавления ее в список зарегистрированных функций парсера.
4.1 Регистро-независимое сравнение символа
Первое расширение, которое мы реализуем - сравнение символа без учета регистра. Делается это следующим образом
Функция ПарсерОдногоСимвола_РегистроНезависимый(Парсер,СтрокаАнализа)
СимволАнализа = Сред(СтрокаАнализа,1,1);
Если НРег(СимволАнализа) = НРег(Парсер.Парсер) Тогда
Возврат РазобраноУспешно(СимволАнализа,Сред(СтрокаАнализа,2));
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
Конецесли;
Конецфункции
Обычно в регулярных выражениях для обозначения регистро-независимого поиска используется префикс "i". Не будем оригинальны, и добавим определения к нашему парсеру
ЗарегистрироватьФункцииПарсера ("imatch", "ПарсерОдногоСимвола_РегистроНезависимый");
Напишем тест для проверки -
Процедура Тест_imatch() экспорт
БукваМ = ПостроитьПарсер("imatch","м");
Результат = ПрименитьПарсер(БукваМ,"Мама");
Образец = РазобраноУспешно("М","ама");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат), Значение(образец));
Конецпроцедуры
4.2 Сравнение со строкой
Второе расширение, которое будет нам необходимо - сравнение со строкой. В принципе это и логично - большинство лексем, которые мы будем извлекать из текста запроса, длиннее одного символа. Определим парсер для оператора "match-string"
Функция ПарсерСтрокиПоОбразцу(Парсер,СтрокаАнализа)
количестоСимволов = СтрДлина(Парсер.Парсер);
строка = Сред(СтрокаАнализа,1,количестоСимволов);
Если строка = Парсер.Парсер Тогда
Возврат РазобраноУспешно(строка,Сред(СтрокаАнализа,количестоСимволов+1));
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
КонецЕсли;
КонецФункции
Добавим определение в инициализацию
ЗарегистрироватьФункцииПарсера ("match-string", "ПарсерСтрокиПоОбразцу");
Напишем тест для проверки -
Процедура Тест_match_string() экспорт
строкаОбразец = ПостроитьПарсер("match-string","select");
Результат = ПрименитьПарсер(строкаОбразец,"select 1");
Образец = РазобраноУспешно("select"," 1");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат), Значение(образец));
Конецпроцедуры
4.3 Регистро-независимое сравнение со строкой
Следующее расширение которое опишем – регистро-независимое сравнение со строкой. Определим парсер для оператора "imatch-string"
Функция ПарсерСтрокиПоОбразцу_РегистроНезависимый(Парсер,СтрокаАнализа)
количестоСимволов = СтрДлина(Парсер.Парсер);
строка = Сред(СтрокаАнализа,1,количестоСимволов);
Если НРег(строка) = НРег(Парсер.Парсер) Тогда
Возврат РазобраноУспешно(строка,Сред(СтрокаАнализа,количестоСимволов+1));
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
КонецЕсли;
КонецФункции
Добавим определение в инициализацию
ЗарегистрироватьФункцииПарсера ("imatch-string", "ПарсерСтрокиПоОбразцу_РегистроНезависимый");
Напишем тест для проверки -
Процедура Тест_imatch_string() экспорт
строкаОбразец = ПостроитьПарсер("imatch-string","select");
Результат = ПрименитьПарсер(строкаОбразец,"seLECt 1");
Образец = РазобраноУспешно("seLECt"," 1");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат), Значение(образец));
Конецпроцедуры
4.4 Интервал
Небольшой пример - пусть нам необходимо реализовать парсер для следующей грамматики
|
‹Number› |
::= |
‹Digits› [{‹dot› ‹Digits›}] |
|
‹dot› |
::= |
. |
|
‹Digits› |
::= |
‹Digit›+ |
|
‹Digit› |
::= |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Во многих лексических сканерах есть альтернативная форма для определения‹Digit›, а именно - интервал
|
‹Digit› |
::= |
[0-9] |
Определим такую функцию
Функция Интервал(Парсер,СтрокаАнализа)
мин = Парсер.Парсер.Минимум;
макс = Парсер.Парсер.Максимум;
символ = Сред(СтрокаАнализа,1,1);
Если мин
Добавим определение в инициализацию
ЗарегистрироватьФункцииПарсера ("range", "Интервал");
Напишем тест для проверки -
Процедура Тест_range() экспорт
цифра = ПостроитьПарсер("range",Новый Структура("Минимум,Максимум","0","9"));
число = ПостроитьПарсер("+",цифра);
Результат = ПрименитьПарсер(число,"1234");
Образец = РазобраноУспешно(ВМассив("1","2","3","4"),"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
юТест.ПроверитьРавенство(Значение(Результат)[1], Значение(образец)[1]);
юТест.ПроверитьРавенство(Значение(Результат)[2], Значение(образец)[2]);
юТест.ПроверитьРавенство(Значение(Результат)[3], Значение(образец)[3]);
Конецпроцедуры
4.5 Сравнение с кодом символа
Неочевидный парсер, который нам потребуется - сравнение с кодом символа. Дело в том, что символы с ASCII-кодом меньше 48 нельзя ввести с клавиатуры (точнее можно, если использовать Alt+код, но такую возможность не рассматриваем), но вот в тексте, прочитанном из файла, такой символ может встретиться. Опишем парсер и подключение к нашей системе
Функция СравнитьСКодом(Парсер,СтрокаАнализа)
Символ = Сред(СтрокаАнализа,1,1);
КодСимвола = КодСимвола(Символ);
Если Парсер.Парсер = КодСимвола Тогда
Возврат РазобраноУспешно(символ,Сред(СтрокаАнализа,2));
Иначе
Возврат РазобраноНеудачно(СтрокаАнализа);
КонецЕсли;
КонецФункции
Добавим определение в инициализацию
ЗарегистрироватьФункцииПарсера ("char-code", "СравнитьСКодом");
Напишем тест для проверки -
Процедура Тест_code() экспорт
//КодСимвола("1") = 49
цифра = ПостроитьПарсер("char-code",49);
число = ПостроитьПарсер("+",цифра);
Результат = ПрименитьПарсер(число,"1234");
Образец = РазобраноУспешно(ВМассив("1"),"234");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат)[0], Значение(образец)[0]);
Конецпроцедуры
4.6 Преобразование результата
Следующий неочевидный парсер, который нам потребуется - функция трансформации результата.
Для чего он нужен? Давайте рассмотрим парсер числа определенный чуть выше и посмотрим что этот парсер Тест_range() возвращает - как видим - это массив, но нужен ли он нам? В AST которое мы будем строить, вероятнее потребуется именно число, прочитанное из строки, а не последовательность прочитанных символов.
Давайте напишем такой парсер
Функция Преобразователь(Парсер,СтрокаАнализа)
_Парсер = Парсер.Парсер;
ФункцияПреобразования = _Парсер.Функция;
Результат = ПрименитьПарсер(_Парсер.Парсер,СтрокаАнализа);
Если Успех(Результат) Тогда
Значение = Вычислить(ФункцияПреобразования+"(Результат)");
Возврат РазобраноУспешно(Значение,Остаток(Результат));
КонецЕсли;
Возврат РазобраноНеудачно(СтрокаАнализа);
КонецФункции
Добавим определение в инициализацию
ЗарегистрироватьФункцииПарсера ("fn", "Преобразователь");
Напишем тест для проверки -
Функция РезультатВЧисло(результат)
строка = "";
для каждого х из результат.Значение Цикл
строка=строка+х;
КонецЦикла;
Возврат Число(строка);
КонецФункции
Процедура Тест_fn() экспорт
цифра = ПостроитьПарсер("range",Новый Структура("Минимум,Максимум","0","9"));
число = ПостроитьПарсер("+",цифра);
Преобразователь = ПостроитьПарсер("fn",Новый Структура("Парсер,Функция",число,"РезультатВЧисло"));
Результат = ПрименитьПарсер(Преобразователь,"1234");
Образец = РазобраноУспешно(1234,"");
юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
юТест.ПроверитьРавенство(Значение(Результат), Значение(образец));
Конецпроцедуры
Конечно примитмвных парсеров можно придумать очень много, но все они будут описываться именно по этой схеме - Функция работы, регистрация в списке доступных парсеров, вызов
В следующей публикации будет рассмотрен вопрос как учитывать позицию символов, кэширование работы парсеров и как разработать простой парсер для языка запросов 1С.
Предложения по улучшению стиля/текста/примеров/алгоритмов вы можете направлять на мой почтовый ящик wwal@yandex.ru