Разработка синтаксического анализатора языка запросов на языке 1С

Публикация № 316338

Разработка - Математика и алгоритмы

parser PEG

Пример разработки генератора для PEG парсера

Разработка синтаксического анализатора языка запросов на языке 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

 

Скачать файлы

Наименование Файл Версия Размер
Код с тестами

.rar 53,47Kb
29.06.15
5
.rar 53,47Kb 5 Скачать

Специальные предложения

Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. Идальго 135 03.12.14 10:07 Сейчас в теме
Нифига себе генератор синтаксических анализаторов)))
2. Famza 83 03.12.14 11:04 Сейчас в теме
3. so-quest 133 03.12.14 11:18 Сейчас в теме
(2) главное что бы понятно было.
4. SeiOkami 1672 03.12.14 16:43 Сейчас в теме
Плюс однозначно. Хотя описано довольно сложно. Надо будет потом хорошенько повникать, а то так с ходу и не понимаю )
5. so-quest 133 03.12.14 18:40 Сейчас в теме
(4) Ну может в следующей части, на реальном примере разбора текста запроса станет ясно. Хотя старался писать просто и мало. В основном все в коде
6. Ekovichev 672 03.12.14 20:10 Сейчас в теме
Статья интересная. Чем это может быть полезно на практике?
NoRazum; boy13; Evil Beaver; +3 Ответить
8. so-quest 133 04.12.14 11:17 Сейчас в теме
(6) Обычно парсеры для разбора строк используются.
(7) Ну если тебе так сильно нужен будет исходный код - вышлю на мыло. Ну или пожди пока эту идиотскую блокировку снимут
7. webester 33 04.12.14 06:17 Сейчас в теме
Исходный код статьи - здесь

РосПозорНадзор запретил гитхаб в нашей стране, там нашлась статья, как неудачник может прекратить свои страдания.
9. ivanov660 2317 04.12.14 11:17 Сейчас в теме
Что-то уж очень запутано и много кода. К тому же конструктор запросов уже существует для управляемого приложения.
На мой взгляд для решения данной задачи проще использовать конечные автоматы или иерархические конечные автоматы. Из плюсов: алгоритм занимает "десяток" строк кода + таблица правил настраиваемых вручную (есть редакторы), которые можно поменять довольно быстро; графическое представление алгоритма.
10. so-quest 133 04.12.14 11:32 Сейчас в теме
11. AlexanderKai 04.12.14 12:53 Сейчас в теме
Я не понял, а внутри архива есть парсер запроса или только тесты?
12. so-quest 133 04.12.14 13:13 Сейчас в теме
Только тесты к тому что описано.
В следующей публикации будет рассмотрен вопрос как учитывать позицию символов, кэширование работы парсеров и как разработать простой парсер для языка запросов 1С.



13. AlexanderKai 04.12.14 14:38 Сейчас в теме
(12)
А сам парсер будет? :)
Просто тесты бессмысленны без того, что они проверяют :)
14. so-quest 133 04.12.14 15:27 Сейчас в теме
(13) Ну а как же без этого? Будут. Правда сперва будет статья про расчет координат, затем обработка ошибок и восстановление состояния, потом кэширование.
А после - любой сможет написать свой парсер - с девами и вином.
15. AlexanderKai 04.12.14 15:30 Сейчас в теме
(14)
Будет интересно посмотреть на конструирование парсера. Сам сейчас занимаюсь подобным - может чего почерпну.
16. so-quest 133 04.12.14 16:00 Сейчас в теме
Ну коли занимаешься - присоединяйся. Гуртом и батьку бить легче, не то что парсер писать :)

Это, кстати, ко всем желающим относиться - если есть идеи о чем стоит написать - говорите.

17. ADirks 184 05.12.14 07:44 Сейчас в теме
А ещё есть такая интересная штука, как теория формальных языков. Её в институтах изучают.
Также есть и практические инструменты, использующие эту теорию. Например классика - yacc и bison - для C/C++. Или GOLD Parser для всего остального.
18. so-quest 133 05.12.14 08:58 Сейчас в теме
Вот здесь http://infostart.ru/public/239061/ разбор выражения с классической зрения. Для ознакомления.
Второе - формальная грамматика для языка запросов в 1С - отдельная песня. Если посмотришь на реализацию от tormozit - всплакнешь. На остальные - вообще лучше не смотреть, даже моих трех коридоров церковно-приходской школы хватает понять - reduce/reduce конфликт это очень прикольно.
А насчет генераторов парсеров - в прошлом году выкладывал свой шаблон для GoldenParser - позволял по граматике сразу строить код 1С - основной вопрос был "что это и зачем?" Снес нафиг. А ты говоришь FSM, CFG....
19. AlexanderKai 05.12.14 12:43 Сейчас в теме
(18)
Если посмотришь на реализацию от tormozit - всплакнешь.

Это про "Подсистема "Инструменты разработчика" v3.20"?
21. AlexanderKai 05.12.14 12:48 Сейчас в теме
(18)
А насчет генераторов парсеров - в прошлом году выкладывал свой шаблон для GoldenParser - позволял по граматике сразу строить код 1С - основной вопрос был "что это и зачем?" Снес нафиг. А ты говоришь FSM, CFG....

Про ГолденПарсер что-то помню такое, что было. Код 1С - всмысле, байт-код?
23. tormozit 6024 05.12.14 13:37 Сейчас в теме
(18) да, в моей грамматике есть ряд кривых моментов, которые появились для обхода reduce/deduce конфликтов. Как говорится красиво сделал до туда, до куда осилил, а дальше залепил бреши. Самое главное, что на основе этой грамматике сейчас работает самый продвинутый из виденных мной конструктор запросов.
24. so-quest 133 05.12.14 13:51 Сейчас в теме
(23) Он не только самый продвинутый, но и единственный которым можно пользоваться. Тебе за него - отдельный респект и уважуха. В принципе если бы не проблемы с комментариями и определением ид это или имя функции - она бы и мне подошла. Но не срослось. Сергей, если обидел тебя своим высказыванием про "плакал" - прошу прощения, но высказывание относилось не к твоему труду, а к gold parser'у - (как к неособо дружелюбной системе разроботки), и КС грамматикам в принципе (перестал их любить)
(21) Нет, в код 1С (текст)
(20) Возьми ты ту же грамматика для языка запросов и попробуй встроить комментарий везде где он может быть - если делать будешь это в GP - то там тебе даже подсветят где происходит конфликт. И подробно разжуют почему. Вообще комментарии в грамматике - отдельная тема.
29. tormozit 6024 05.12.14 18:32 Сейчас в теме
(24) Да, среда разработки GoldParser конечно отвратная. Видимо все потому, что она кроссплатформенная и потому интерфейс пользователя такой корявый и неудобный. Но альтернатив, таких же доступных для применения в 1С, я не видел. Еще есть такая крутая штука ANTLR http://www.antlr.org/ , но для него нет готового COM движка.
Да, в грамматике моей самая большая беда с идентификаторами. Я много времени убил на поиск красивого решения, но так и не нашел. Мне даже кажется, что его там в принципе нельзя сделать красивым.
И по комментариям тоже кривая реализация конечно, из-за чего я ее так долго и не делал, т.к. искал опять же красивое решение. В некоторых местах комментарии вызывают ошибку парсера, т.к. они лексемы рождают, которые несколько правил могут начинать. Но уже мой код в 1С там выдает подсказку пользователю, чтобы он переставил комментарии. И за разбор комментариев таким образом пришлось заплатить некоторым снижением гибкости расположения комментариев в тексте запроса.
20. AlexanderKai 05.12.14 12:46 Сейчас в теме
reduce/reduce конфликт это очень прикольно.

Если в двух словах? :)
22. tormozit 6024 05.12.14 13:30 Сейчас в теме
Правильное название GOLD Parsing System, а не Golden Parser. Грамматика гибридного языка запросов (1с + sql) для этого парсера мной развивается тут http://devtool1c.ucoz.ru/load/grammatika_jazyka_zaprosov_1s_8_2_goldparser/1-1-0-4. Она используется в подсистеме "Инструменты разработчика" при построении дерева запроса в консоли запросов и в собственном конструкторе запроса.
25. AlexanderKai 05.12.14 14:20 Сейчас в теме
Возьми ты ту же грамматика для языка запросов и попробуй встроить комментарий везде где он может быть - если делать будешь это в GP - то там тебе даже подсветят где происходит конфликт. И подробно разжуют почему. Вообще комментарии в грамматике - отдельная тема.

С трудом представляю какие могут быть сложности с комментариями.
26. so-quest 133 05.12.14 15:01 Сейчас в теме
Ссылку на грамматику тебе дали. Скачай и добавь поддержку комментариев. Проблему поймешь легко.
Вот пример

"ВЫБРАТЬ // хитрый комментарий 1
| 1 // хитрый комментарий 2
| КАК // хитрый комментарий 3
|Поле1 // хитрый комментарий 4
|// хитрый комментарий 5
|ОБЪЕДИНИТЬ // хитрый комментарий 6
| // хитрый комментарий
|ВСЕ // хитрый комментарий 7
|
|ВЫБРАТЬ // хитрый комментарий 8
| // хитрый комментарий 9
|2"

Если сделаешь - тебе спасибо очень многие скажут. :)

27. AlexanderKai 05.12.14 15:34 Сейчас в теме
(26)
Я грамматиками не балуюсь - просто беру и пишу :) Как-то пробовал вникнуть в него(голден парсер) - но что-то у нас характеры не сошлись.
Возможно это архитектурные ограничения Голден Парсера и там действительно это проблема. В моей текущей версии парсера комментарии легко обрабатываются. До запроса еще не дошел, но не думаю, что там возникнут проблемы.
30. tormozit 6024 05.12.14 18:39 Сейчас в теме
(27) AlexanderKai,
В моей текущей версии парсера комментарии легко обрабатываются.
Грамматика языка запросов 1с в несколько раз сложнее грамматики встроенного языка 1с и соответственно неоднозначных переходов к правилам при встрече лексемы комментария будет намного меньше.
43. orefkov 2067 26.12.14 12:10 Сейчас в теме
(26)
А зачем комментарии встраивать в грамматику? Может я что-то не понимаю? Но обычно комментарии еще на этапе лексического разбора убираются.
void ModuleParser::nextLexem()
{
	for(;;)
	{
		m_source.nextLexemWithKeyword(lastLexem);
		if(lexRemark == lastLexem.type)
			continue;
...

Показать
44. so-quest 133 26.12.14 12:34 Сейчас в теме
(43) orefkov, глянь на инструменты разработчика - по AST восстанавливается текст запроса.
45. orefkov 2067 26.12.14 13:09 Сейчас в теме
(44)
А, я так понимаю это задача о "конструкторе запросов, оставляющем комментарии" ?
То есть залили текст, поредактировали дерево, вылили текст?
Имхо, это уже не задача грамматики.
Вернее, в терминах одной лишь грамматики строго не реализуема. Ибо грамматика - это прежде всего формальный подход, регламентирование, строгость и однозначность.
А какие формальные правила есть при написании комментариев? Нет, и быть не может. К примеру, стоит комментарий перед первым полем в списке полей. Он к чему относится? К конкретному полю:
"-- Тут поле Товара"
А может ко всему списку полей:
"-- Начинаем поля выборки"
А может просто
"-- Вася 10.10.10 был и правил"
Поле "Товар" удалили - оставлять комментарий или нет?

Я бы все-таки и для этой задачи не вносил комментарии в грамматику. А сделал бы комментарий просто атрибутом узла AST. На этапе лексического разбора комментарий бы просто привязывал к ближайшей последующей/предыдущей значащей лексеме. В-принципе, при желании, после построения дерева (уже после парсера грамматики) можно было бы по нему пройтись и выделить комментарии в отдельные узлы. Но так и так - это уже задача семантики, а не грамматики - следующее звено цепочки "лексика-грамматика-семантика". Грамматика - не серебрянная пуля, и не надо стараться только на ней одной выехать.
Для того же телепата и снегопата ряд моментов мной был вынесен из грамматики на уровень семантики - ибо на уровне грамматики они решались сложно, а на уровне семантики - легко. К примеру "Возврат Выражение" - то, что это допустимо только в функции, но не в процедуре, что Прервать/Продолжить допустимо только в циклах, те же ключевые слова как имена свойств/методов - это уже делалось либо пост-парсером, либо хаком разбора.
46. tormozit 6024 26.12.14 13:25 Сейчас в теме
(45) +1
В (29) я этой проблемы касался относительно комментариев. Была бы возможность (в COM-реализации парсера GoldParser), я бы ни за что не стал осквернять грамматику комментариями. Но кроме бесспорных минусов у варианта обозначения комментариев в грамматике есть и свои плюсы, которые конечно же не смогут перевесить минусы.
47. so-quest 133 27.12.14 18:16 Сейчас в теме
(46) tormozit, Была бы возможность (в COM-реализации парсера GoldParser), я бы ни за что не стал осквернять грамматику комментариями. - там же вроде в зависимости от того что вернет Parse() можно определить токен это или комментарий. могу ошибаться - сто лет туда не смотрел
48. tormozit 6024 27.12.14 21:54 Сейчас в теме
(47) определять что лексер вернул комментарий, можно, но прикреплять к узлам синтаксического дерева эти комментарии напрямую нельзя. Дерево все полностью только для чтения.
67. tormozit 6024 08.07.20 12:07 Сейчас в теме
(48) В итоге я доработал парсер https://github.com/tormozit/GoldParserEngine - добавил объекту Token свойства BeginNoise и EndNoise для получения комментариев уже в 1С. А правила грамматики https://github.com/tormozit/Query-language-grammar-for-1C избавил от комментариев.
68. orefkov 2067 08.07.20 13:27 Сейчас в теме
(67)
27.12.14 - 08.07.20
Старая гвардия просыпаться начала :)
70. so-quest 133 08.07.20 14:12 Сейчас в теме
28. so-quest 133 05.12.14 15:52 Сейчас в теме
Другими словами - ты пишешь то же самое что и я - нисходящий разбор. Интересно посмотреть - как без баловства с грамматиками можно что-то сделать.
> До запроса еще не дошел, но не думаю, что там возникнут проблемы.
При ручном разборе - нет. Не возникнут. Как впрочем и проблем с "|" не будет - у тебя оператор выбора - упорядоченный получается.
> Возможно это архитектурные ограничения Голден Парсера и там действительно это проблема.
Нет. В GP - не проблема, в общем случае для КС это сложно.
31. so-quest 133 05.12.14 19:09 Сейчас в теме
> Мне даже кажется, что его там в принципе нельзя сделать красивым.
В той постановке задачи которую принял ты - да, лучше чем у тебя сделано - никак. Повторюсь - это проблема всех КС грамматик - прочитан символ и надо определиться куда дальше идти - если сверток больше 2 - конфликт. В PEG этого нет, так как оператор альтернативы - упорядочен. Кстати, если упростить твою грамматику до одного id, а выяснение роли этого идентификатора (идентификатор ил имя функции) вынести в интерпретатор - то грамматика упрощается.


Проблема с комментариями решается просто на уровне GP. Но тогда тебе придется очень много кода перекроить. При чтении комментария генерируется токен с типом комментарий (даже 2 - что бы различать многострочные и однострочные комментарии). Можно на это завязаться - но повторюсь - много переписывать придется.


>Грамматика языка запросов 1с в несколько раз сложнее грамматики встроенного языка 1с
Ну не сказал бы. В инете валяется грамматика которую еще MMF (по моему он, могу ошибаться) делал. Посложнее языка запросов будет.
32. tormozit 6024 05.12.14 20:04 Сейчас в теме
(31)
При чтении комментария генерируется токен с типом комментарий
Это я в первую очередь пробовал, но COM-объекты неизменяемые, т.е. я не могу в них вставлять свои дополнения (прилепить комментарий к текущему правилу). Поэтому такой способ кажется все очень усложнит.

Под сложностью грамматики имею ввиду некую функцию от количества правил и средней степени вложений правил. Мне кажется грамматика встроенного языка тут явно проще будет.
33. alexinzaz 08.12.14 14:56 Сейчас в теме
Мне чет институтские лекции по основам трансляторов вспомнились:) Автору респект.
34. so-quest 133 15.12.14 21:06 Сейчас в теме
Ищу соавтора на вторую часть статьи. Будут описано построение парсера для арифметических выражений, построение генератора парсера и в качестве примера использования - описана грамматика markdown-разметки для комментариев в коде. Желающие - отметьтесь в личке.
35. brr 179 19.12.14 15:58 Сейчас в теме
(34) не могу написать вам личное сообщение, потому что у меня менее 1 стратмани :))). Готов поучаствовать.
36. orefkov 2067 25.12.14 11:30 Сейчас в теме
В снегопате процедура парсинга языка 1С состоит из 3 тысяч строк, в которых 1300 "goto" и ни одного "for" :)
37. so-quest 133 25.12.14 11:51 Сейчас в теме
38. orefkov 2067 25.12.14 11:52 Сейчас в теме
(37)
Нет конечно. Сначала bison, а потом мой скрипт по его результатам.
39. so-quest 133 25.12.14 11:54 Сейчас в теме
Косяк с х.Возврат обошел так же как и в 77?
40. orefkov 2067 25.12.14 12:34 Сейчас в теме
(39)
Немного недопонял, какой именно косяк и как я его обошел в 77?
41. so-quest 133 25.12.14 12:40 Сейчас в теме
Использование ключевых слов в имени полей. В грамматике для 77 ты ввел отдельное правило, а здесь как сделал?
42. orefkov 2067 26.12.14 09:54 Сейчас в теме
(41)
int ModuleParser::onError()
{
    if ((stack->state == 73 || stack->state == 75 || stack->state == 149) && lastTokenType > lexName) {
        // Имена после точки могут совпадать с ключевыми словами
        lastTokenType = lexName;
        return 1;
    }
    return 0;
}
Показать
49. Makushimo 157 30.12.14 06:02 Сейчас в теме
Как использовать эти алгоритмы?
Это вот что?
Проверим работоспособность парсера последовательности на следующей грамматике
‹тест1› ::= ‹м› ‹а› ‹м› ‹а›
‹м› ::= м
‹а› ::= а

с этим что делать?

Красиво изложенная теория не укладывается в голове.ни вдоль ни поперек.

Как подать на вход некий текст? Пример кода
Что будет на выходе?
Отсюда должно появиться понимание - Для чего все это?

Помню летал в вышмате в институте, но уже тогда кроме игр разума, не видел практической ценности этих знаний. Не показали, блин -)))

50. so-quest 133 30.12.14 10:14 Сейчас в теме
(49) Makushimo, вот буквально после грамматики ниже - процедура Тест_Последовательно() - в ней пример вызова для анализа строки и анализ полученного результата.
Практическая ценность - конечно никакой. Одна игра разума. Возможно во второй части будет понятней - https://github.com/wwall/report-1/blob/master/md/part2.md - но опять же, повторюсь, для написания печатных форм и при подготовке к экзамену в 1С - никак не поможет.



51. Makushimo 157 30.12.14 14:31 Сейчас в теме
(50)
тю на вас -))
я изучаю всякие подходы к реализации разного рода анализаторов/парсеров текстов
думал можт у вас научусь чему нибудь.
А тут ничего не понятно-)))
52. Makushimo 157 30.12.14 14:44 Сейчас в теме
(50)
почему тест грамматики написан именно таким "убеймозг"-образом ??
это строка, которую можно подать на вход процедуре/функции?

например
СтрокаНаАнализ = "‹тест1› ::= ‹м› ‹а› ‹м› ‹а›
| ‹м› ::= м
| ‹а› ::= а";
Результат = Тест_Последовательно(СтрокаНаАнализ);

Но в тексте статьи "вот буквально после грамматики ниже" ничего такого нету.
Там прямо в процедуре что-то происходит -))
ну то есть понять что с объектами происходит можно, но что это в итоге означает - нет.
Например, Образец получает результат разбора части строки.
а нечто с именем юТест имеет метод ПроверитьРавенство() который что-то делает.
и т.д.
53. so-quest 133 30.12.14 14:51 Сейчас в теме
(52) Makushimo, Там прямо в процедуре что-то происходит -))
Вы попутно с " всякими подходами к реализации разного рода анализаторов/парсеров текстов" язык программирования 1С еще изучите. Станет понятно что там происходит, почему и как.
И очень рекомендую прочитать часть озаглавленную "Необязательные инструменты использованные в процессе разработки" - станет ясно откуда берется юТест и почему код построен таким образом.
54. Makushimo 157 31.12.14 05:27 Сейчас в теме
(53)
О, так еще и язык 1С учить надо? -)))
е-мае, куда я попал. -)))

короче на вас надежды нет, придется идти тем же, что и вы или другим путем.

и да, вы нереально круты раз написали такую непонятную крутую штуку. -))
55. so-quest 133 31.12.14 12:02 Сейчас в теме
За разрешение двигаться - тебе конечно спасибо. Я то думал что же меня так на месте держит?

Вот объясни что тебе не понятно??? Это же заготовка под PEG генератор. Что может быть проще??? Все ссылки объясняющие для чего это делается есть. Как это делается - в коде написано. Что будет сделано дальше - также написано.

А насчет непонятности - ты первый на 3 ресурсах кто об сказал. Может действительно стоит переписать.

С наступающим. Всех благ в новом году!
56. Dethmontt 09.01.15 03:23 Сейчас в теме
(55) ну когда уже ждать оформленного "тут" продолжения?
57. sashocq 191 02.02.15 11:37 Сейчас в теме
Тут действительно очень нелегко разобраться что к чему. Описания принципов сильно переплетено с деталями реализации. Описаны кирпичи, но нет какого-нибудь сарая, который можно из них построить. Примеры как разложить строку в массив символов и обратно или как "1234" преобразовать в 1234 мне совершенно не интересны. Мне интересно как из строки "128 + -65" получить число 63. При чем, желательно видеть где начало, а где конец. У вас же в тестах сложно увидеть вход и выход, они где-то в середине:
    цифра = ПостроитьПарсер("range",Новый Структура("Минимум,Максимум","0","9"));
    число = ПостроитьПарсер("+",цифра);
    Преобразователь = ПостроитьПарсер("fn",Новый Структура("Парсер,Функция",число,"РезультатВЧисло"));
    Результат = ПрименитьПарсер(Преобразователь,"1234");
    Образец = РазобраноУспешно(1234,"");
    юТест.ПроверитьРавенство(Результат.Тип, образец.Тип);
    юТест.ПроверитьРавенство(Остаток(Результат), Остаток(образец));
    юТест.ПроверитьРавенство(Значение(Результат), Значение(образец));
Показать

Где тут видно, что вход - "1234", а выход - 1234???
58. so-quest 133 02.02.15 13:10 Сейчас в теме
(57) sashocq, насчет такого сравнения прав - тут не очевидно, но в следующей версии поправлено.
Код
цифра = ПостроитьПарсер("range",Новый Структура("Минимум,Максимум","0","9"));
    число = ПостроитьПарсер("+",цифра);
    Преобразователь = ПостроитьПарсер("fn",Новый Структура("Парсер,Функция",число,"РезультатВЧисло"));
    Результат = ПрименитьПарсер(Преобразователь,"1234");
    Образец = РазобраноУспешно(1234,"");
    юТест.ПроверитьРавенствоРекурсивно(Результат образец);

будет думаю более читаемым (но опять же - для этого придется взять мой правленый xUnit, с поддержкой этой функции).
>>> Мне интересно как из строки "128 + -65" получить число 63.
Пишу, вот честно - пишу продолжение, но сейчас увы времени мало. Да и отзывов на бета версию второй части тоже немного, что не добавляет оптимизма. :)
59. baton_pk 402 17.02.15 22:07 Сейчас в теме
вселенская несправедливость, что тут до сих пор не было моего плюса.

Код большими кусками плохо воспринимается.

    МассивРезультата = Новый Массив;
    МассивРезультата.Добавить("м");
    МассивРезультата.Добавить("а");
    МассивРезультата.Добавить("м");
    МассивРезультата.Добавить("а");

Делайте на такие вещи всё-таки функцию какую-нибудь, чтобы тут в статьях это была одна строчка, а не 5.

(Ушёл внимательно читать ещё разок...)
60. artamir 8 08.12.16 16:57 Сейчас в теме
Нашел очепятку в параграфе 3.1 в пункте 1. прочатнный = прочитанный
61. artamir 8 08.12.16 18:11 Сейчас в теме
В параграфе 4.4 исчезло окончание кода:
Функция Интервал(Парсер,СтрокаАнализа)
    мин = Парсер.Парсер.Минимум;
    макс = Парсер.Парсер.Максимум;
    символ = Сред(СтрокаАнализа,1,1);
    Если мин
62. so-quest 133 08.12.16 23:14 Сейчас в теме
Спасибо за комментарии. В следующей версии будет все поправлено.
63. kote 520 12.03.17 19:29 Сейчас в теме
64. tsukanov 13.08.17 16:28 Сейчас в теме
Фигасе ты заморочился )))
65. so-quest 133 14.08.17 08:55 Сейчас в теме
Давно это было. Сейчас бы делал совсем по другому.
66. Darklight 24 08.07.20 09:41 Сейчас в теме
Прошло более 4-х с половиной лет - а продолжение так и не последовало - зато статья всплыла из-загашников инфостарта....
69. so-quest 133 08.07.20 14:04 Сейчас в теме
ИМХО - не нужно это никому. Особо большого толка в парсере запросов нет. Да и этот peg-движок не без греха. В общем разработку можно считать мертвой. Доделывать точно не буду в этом стиле.
Оставьте свое сообщение

См. также

Нечеткое сравнение строк. Метод Джаро-Винклера на 1С Промо

Математика и алгоритмы v8::УФ 1cv8.cf Абонемент ($m)

Схожесть строк. Метод Джаро-Винклера. В обработке реализован алгоритм нечеткого сравнения строк.

3 стартмани

20.04.2018    20089    80    Serg1701    19    

Решение задачи Эйнштейна на платформе 1с

Математика и алгоритмы v8 Абонемент ($m)

Недавно мне попалась интересная задача по созданию обработки, которая будет решать "задачу Эйнштейна". Изначально кажется, что можно просто прописать все явные и неявные условия через "Если", но это не верно. При таком подходе задачу решает ваш мозг, а решить задачу должна сама обработка основываясь только на условиях явно прописанных в тексте. Разработчик не должен делать никаких выводов и прописывать косвенные условия вытекающие из условия задачи. Условия задачи в коде должны переставляться в любом сочетании и это не должно влиять на решение.

1 стартмани

12.08.2020    1051    0    itmind    2    

Treemapping. Демонстрационная обработка

Математика и алгоритмы Работа с интерфейсом v8 1cv8.cf Абонемент ($m)

Пример реализации диаграммы вида Treemap на 1С

1 стартмани

27.02.2020    3057    9    randomus    4    

Рекомендательный сервис на основе коллаборативной фильтрации на 1С. Расширение формы подбора для УТ 11.4

Оптовая торговля Розничная торговля Практика программирования Математика и алгоритмы v8 ERP2 УТ11 КА2 Розничная и сетевая торговля (FMCG) Оптовая торговля, дистрибуция, логистика Россия УУ Абонемент ($m)

В данной разработке реализован механизм рекомендаций товаров по принципу схожести товаров в корзине на основе алгоритма Item-to-Item от Amazon. Разобран алгоритм с демо базой и сделано расширение для УТ11.4 которое добавляет в форму подбора таблицу рекомендаций. Протестировано на 8.3.13.1865 на Управление торговлей, редакция 11 (11.4.8.63)

3 стартмани

25.09.2019    10269    12    informa1555    24    

Определение кратчайших путей, критических путей одним запросом Промо

Математика и алгоритмы v8 1cv8.cf Абонемент ($m)

Еще два примера применения алгоритма каскадного матричного умножения, впервые описанного в статье «Транзитивное замыкание запросом» http://infostart.ru/public/158512/

1 стартмани

07.04.2014    37644    22    ildarovich    31    

Конвейер проверки качества кода

Инструментарий разработчика Практика программирования Математика и алгоритмы v8 1cv8.cf Абонемент ($m)

Jenkinsfile для выполнения проверки качества кода. Собирает информацию с АПК, EDT и BSL-LS. Сопоставляет ошибки с гит-репозиторием, выгруженным ГитКонвертором. Отправляет в Сонар.

3 стартмани

04.09.2019    24572    22    Stepa86    45    

Алгоритмы и регламентные задания (расширение)

Математика и алгоритмы Прочие инструменты разработчика v8 v8::УФ 1cv8.cf Абонемент ($m)

Универсальный механизм для создания алгоритмов и регламентных задач.

5 стартмани

28.05.2018    11733    7    pm74    39    

Многопоточная выгрузка одного сообщения обмена

Практика программирования Математика и алгоритмы Перенос данных из 1C8 в 1C8 v8 Абонемент ($m)

Публикация описывает, как можно распараллелить выгрузку одного сообщения обмена.

1 стартмани

05.12.2016    15599    1    zhichkin    24    

Полная методичка к курсу "Программирование 8.2" Промо

Математика и алгоритмы v8 1cv8.cf Абонемент ($m)

580 страниц знаний! Публикую методичку, а точнее стенограмму курса по подготовке программистов 8.2.

10 стартмани

09.01.2014    52362    110    GROOVY    100    

1С+Классы. Версия-0

Разработка внешних компонент Математика и алгоритмы v8 1cv8.cf Абонемент ($m)

Разработано ООП-расширение языка 1С, включающее (но не ограничивающееся): Классы как абстрактные типы данных с элементами «переменная», «свойство», «функция», «процедура»; Интерфейсы как абстрактные классы без элементов состояния («переменная») и без привязки к реализации методов (свойств, процедур, функций) при определении; Имплементация (реализация) интерфейсов классами; - одиночное открытое наследование; Области видимости «внутренняя» (private), «экспорт» (public), «защищенная» (protected); Статические элементы классов (общие для всех экземпляров класса); Замещение (переопределение реализации) методов при наследовании – «виртуальные методы, свойства»; Сокрытие (затенение) обычных (не замещаемых) элементов при наследовании; Перегрузка процедур и функций по количеству и типам данных аргументов; Конструкторы класса; Деструктор класса; Слабые ссылки; Делегаты.

1 стартмани

28.10.2016    20577    1    IntelInside    68    

Генетический алгоритм для решения простой задачки

Математика и алгоритмы v8 1cv8.cf Абонемент ($m)

Генетический алгоритм в решении задачи: Необходимо расставить правильно (по другому) скобки, чтобы получилось 850 (1 + 2) (3 + 4) (5 + 6) (7 + 8) (9 + 10) (11 + 12) (13 + 14) + 15

1 стартмани

26.09.2016    9869    5    eugeniezheludkov    4    

Объектные блокировки

Практика программирования Математика и алгоритмы v8 Россия Абонемент ($m)

При работе с объектными данными (справочники, документы, планы счетов и т.д.) система «1С:Предприятие» обеспечивает два вида объектных блокировок: пессимистическую и оптимистическую. Они позволяют выполнять целостные изменения объектов при одновременной работе нескольких пользователей.

1 стартмани

17.08.2016    30865    9    Ranis1286    5    

Еще один взгляд на проблему «жизнь без последовательностей». Часть вторая (практическая) Промо

Математика и алгоритмы v8 КА1 БП2.0 УТ10 Розница УПП1 УНФ Россия Абонемент ($m)

В [1 - http://infostart.ru/public/62938/] был предложен метод корректировки списаний по партиям при изменении документов задним числом. Использование данного метода позволяет контролировать остатки при неоперативном проведении и поддерживать учет по партиям всегда в актуальном состоянии, то есть обходиться без механизма последовательности документов. Собственно метод заключался в решении задачи правильного списания по партиям как задачи линейного программирования. В доказательство работоспособности метода приводится следующая «каркасная» конфигурация «Полигон», в которой этот метод реализован.

1 стартмани

19.08.2010    29852    18    ildarovich    35    

Использование методов глобального контекста в системе компоновки данных или недокументированные возможности СКД

Практика программирования Математика и алгоритмы v8::УФ v8::СКД 1cv8.cf Абонемент ($m)

Прочитал на форуме, что СтрРазделить() и ТекущаяДата() работают в языке выражений системы компоновки данных. Почитал справку, там ничего не нашёл, поэтому не поверил. Посмотрел на практике – и правда работает… В данной статье попытался привести весь перечень методов, работающих в СКД, но не описанных в документации.

1 стартмани

05.08.2016    37338    26    klinval    40    

Еще один способ расчета остатков на каждый день в запросе

Математика и алгоритмы Практика программирования v8 Абонемент ($m)

Предлагается новый способ расчета остатков на каждый день (час, минуту, секунду) в запросе. Способ не требует предварительного формирования таблицы дат и также подходит для расчета курсов валют, цен номенклатуры и других периодических сведений на каждую дату периода. На больших объемах данных предлагаемый способ может превосходить по быстродействию ранее известные методы из-за линейной (в лучшем случае) зависимости трудоемкости от длины периода.

1 стартмани

24.04.2016    34502    48    ildarovich    23    

Пример рекурсивной выгрузки иерархической структуры в XDTO

Математика и алгоритмы Внешние источники данных WEB v8 1cv8.cf Абонемент ($m)

Решил реализовать иерархию в пакете XDTO и выгрузить ее рекурсивно. Задача оказалась нетривиальной, хотя и весьма простой. Изысканиями решил поделиться с народом, чтобы не пропадало народное добро.

1 стартмани

26.02.2016    33748    16    starik-2005    3    

Включаем звук в 1С. Доступно и всерьез. Промо

Математика и алгоритмы Универсальные функции v8 1cv8.cf Абонемент ($m)

Как сделать воспроизведение звука в 1С без внешних компонентов? Решаем средствами интернета. Для тонкого, толстого и web-клиента.

1 стартмани

30.12.2013    81577    151    sikuda    37    

Нелинейная многомерная оптимизация - это просто. Часть 3. Имитация отжига

Инструментарий разработчика Математика и алгоритмы Универсальные функции v8 1cv8.cf Абонемент ($m)

Метод имитации отжига для поиска оптимального решения. И, как обычно, универсальная функция поиска этого самого решения.

1 стартмани

13.10.2015    18678    24    dusha0020    5    

Нелинейная многомерная оптимизация - это просто. Часть 1. Градиентный спуск

Математика и алгоритмы Универсальные функции Практика программирования v8 Абонемент ($m)

Рассказ с демонстрацией возможностей градиентного метода поиска оптимального решения.

1 стартмани

07.07.2015    18365    8    dusha0020    19    

Пример сериализации объектов в 1С 8.3 и их восстановления из сериализованных данных

Математика и алгоритмы Обмен через XML Перенос данных из 1C8 в 1C8 v8 1cv8.cf Россия Абонемент ($m)

Простейшая обработка, демонстрирующая принципы сериализации выбранного Объекта 1С (Справочника, Документа и еще некоторых), с возможностью восстановления Объекта из сериализованного значения.

1 стартмани

05.07.2015    28428    75    katkov_a    29    

Конспект лекций по курсу «Автоматизированные информационные системы» Промо

Математика и алгоритмы v8 1cv8.cf Россия Абонемент ($m)

Конспект лекций по курсу «Автоматизированные информационные системы» составлен на основании требования Государственного образовательного стандарта среднего профессионального образовании к содержанию и уровню подготовки выпускника по специальности 230103 «Автоматизированные системы обработки информации и управления». В конспекте есть общие сведения о методике 1С:Профкейс. Конспект лекций разработал: канд. техн. наук, доцент Космачев С.Н.

1 стартмани

07.06.2012    24838    9    ksnik    19    

Написание простой обработки через тестирование

Математика и алгоритмы Практика программирования v8 1cv8.cf Абонемент ($m)

Раньше я считал, что в 1С невозможно юнит-тестирование (ведь тут нет вездесущих объектов, привычных классов и и. т.). Иногда на Инфостарте появлялись специализированные обработки, но часто они скорее отпугивали от темы тестирования, чем привлекали к ней. Потом я узнал про xUnitFor1C. Оказалось, что тестирование в 1С в общем не так уж и сложно, даже в сравнении с другими языками. В данной статье я расскажу о своем первом опыте.

1 стартмани

24.02.2015    27700    12    Alien_job    40    

Парсинг сайта без использования встроенного браузера для начинающих

Практика программирования Математика и алгоритмы WEB v8 1cv8.cf Абонемент ($m)

Данная статья показывает пример парсинга одного конкретного сайта со своей структурой, на основе которого можно понять суть, методы и без особых проблем парсить другие сайты. Здесь описано: подключение к серверу, сохранение html-страницы на диск, загрузка и цикличный обход ее структуры, рекурсивный парсинг, загрузка картинок по ссылке на диск.

1 стартмани

20.11.2014    40761    117    angernaughts    37    

Куайн (Программа, выводящая свой исходный код на экран)

Математика и алгоритмы Практика программирования v8 1cv8.cf Абонемент ($m)

Обработка позволяет насладится реализацией этой интересной, и совершенно бесполезной с практической точки зрения задачей.

1 стартмани

25.08.2014    9897    0    atridis    7    

Автоформатирование кода Промо

Сервисные утилиты Обработки Чистка базы Справки Производительность и оптимизация (HighLoad) Инструментарий разработчика Практика программирования Универсальные обработки Решение задач на 1С:Специалист Математика и алгоритмы Администрирование данных 1С Разработка Тестирование и исправление Стартеры 1С v8 1cv8.cf Абонемент ($m)

Как часто приходится работать в режиме аврала, когда на оформление кода не хватает времени? И как лениво порой бывает, возвращаться к уже рабочему коду, что бы отформатировать его и привести в порядок. Данная обработка позволяет автоматически форматировать текст кода, в соответствии с настройками пользователя. Это позволит привести ваш код, как уже написанный так и будущий к единому оформлению.

1 стартмани

19.12.2012    40625    46    Sibars    57    

Пророк в своем отечестве или Читаем XML с помощью XDTO

Математика и алгоритмы v8 1cv8.cf Абонемент ($m)

Проходи, садись поудобнее и приготовься внимать мне. Сейчас я предскажу твое будущее… В этом пакете я описываю схему XML… Вы можете видеть, что код очень прост, и никакого преобразования элементов прочитанного XML не требуется…

1 стартмани

29.01.2014    45652    24    majmyl    53    

БСП, использование типового механизма 1с для запуска регламентных заданий с заранее подготовленными настройками.

Математика и алгоритмы БСП (Библиотека стандартных подсистем) v8 1cv8.cf Абонемент ($m)

Описание варианта запуска регламентного задания на БСП, без изменения типовой конфигурации.

1 стартмани

03.01.2014    36836    114    almas    7    

Методический материал. Работа с запросами

Практика программирования Математика и алгоритмы v8 Абонемент ($m)

Краткое методическое пособие для изучения запросов в 1С

1 стартмани

23.12.2013    18207    12    rayastar    27    

Определитель матрицы

Практика программирования Математика и алгоритмы v8 Абонемент ($m)

Представлена программная реализация вычисления определителя матрицы посредством языка запросов 1С. Даны два метода: 1) прямой, на основе определения 2) метод Гаусса, приведение к диагональному виду с вычислением произведения диагональных элементов. В обновленной версии в случае целочисленных элементов для метода Гаусса проводится полная факторизация всех чисел, участвующих в вычислениях.

1 стартмани

28.11.2013    12787    8    zaxarovsky    8    

Квадратный корень в запросе 1С

Практика программирования Математика и алгоритмы v8 Абонемент ($m)

Язык запросов 1С не позволяет вычислить квадратный корень 1С. Квадратный корень может пригодиться при вычислении среднеквадратического отклонения или геометрических вычислениях.

1 стартмани

24.10.2013    27293    4    Elisy    53    

Задачи о 5 и 9 ферзях

Математика и алгоритмы v8 Абонемент ($m)

Задача о ферзях-часовых. На шахматной доске надо расставить 5 ферзей, чтобы они держали под боем все клетки доски. Задача В. Франгена, расставить на шахматной доске 10 “белых” и 9 “чёрных” ферзей так, чтобы ни один из них не находился под ударом противника

1 стартмани

31.08.2013    21016    0    scientes    4    

Расчет SHA-1 хеша средствами 1С. Битовые операции в 1С или урок двоичной математики

Практика программирования Математика и алгоритмы v8 1cv8.cf Россия Абонемент ($m)

Расчет хеша SHA-1 без использования каких-либо внешних компонет - возможно ли это в 1Cv8? Оказывается вполне возможно!

1 стартмани

13.03.2013    31434    76    Антон Ширяев    40    

Анализ цикломатической сложности кода

Инструментарий разработчика Разработка внешних компонент Математика и алгоритмы v8 1cv8.cf Абонемент ($m)

Инструмент для контроля сложности написанного кода для 1С8. Пригодится разработчикам, которые заботяться о качестве своего продукта, простоте его тестирования и дальнейшей поддержке.

1 стартмани

13.12.2012    25394    66    Spitfire    30    

Подсистема допроведения документов

Практика программирования Математика и алгоритмы v8 БП2.0 УТ10 УПП1 Россия Абонемент ($m)

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

1 стартмани

01.10.2012    14369    4    SergAn    40    

Основы тестирования доработок

Математика и алгоритмы v8 1cv8.cf Россия Абонемент ($m)

Попытался 1С консультантам-новичкам объяснить принципы тестирования доработок, принимаемых у программистов 1С. И описать частые ошибки в самих доработках, и какие действия нужны для обнаружения этих ошибок (без погружения в код).

1 стартмани

20.08.2012    27096    8    1СERP    17    

Простая и элегантная форма выбора из ТЗ

Математика и алгоритмы v8 1cv8.cf Россия Абонемент ($m)

Простая в использовании форма выбора из ТЗ. Можно использовать как общюю форму (весь код в модуле формы).

1 стартмани

31.05.2012    11099    0    mozz    3    

"Внешний" справочник или Хранение данных между сеансами работы внешних обработок

Математика и алгоритмы v8 Россия Абонемент ($m)

Часто встает задача хранения неких данных между сеансами работы с внешними обработками или печатными формами, а конфигурация находится на поддержке. Хранить на жестком диске в файлах? Нее...

1 стартмани

29.02.2012    21766    10    Damian    34    

Перенос строк из табличной части в табличную часть любого документа (обычное приложение)

Математика и алгоритмы v8 1cv8.cf Абонемент ($m)

Перенос строк из табличной части в табличную часть любого документа (обычное приложение)

1 стартмани

15.11.2011    7208    10    alexnikolas    16    

Горячие клавиши 1С 8

Математика и алгоритмы v8 1cv8.cf Россия Абонемент ($m)

Порядка 200 комбинаций сочетаний клавиш для работы в 1с - конфигуратор и само приложение. По предложению pumbaE список команд был дополнен и расширен, за что ему отдельное спасибо :)

1 стартмани

09.03.2011    12770    10    nzass    29    

Связи Метаданных. Построитель SQL запросов

Математика и алгоритмы v8 1cv8.cf Россия Абонемент ($m)

Для написания SQL запросов часто требуется знать взаимосвязи объектов Метаданных. Данная обработка помогает решить проблему написания SQL запросов с учетом взаимосвязей объектов Метаданных.

1 стартмани

21.11.2010    21928    29    shishkin1966    24