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

29.06.15

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

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

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

Наименование Файл Версия Размер
Код с тестами
.rar 53,47Kb
6
.rar 53,47Kb 6 Скачать

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

 

parser PEG

См. также

Метод Дугласа-Пойкера для эффективного хранения метрик

Математика и алгоритмы Платформа 1C v8.2 Конфигурации 1cv8 Россия Абонемент ($m)

На написание данной работы меня вдохновила работа @glassman «Переход на ClickHouse для анализа метрик». Автор анализирует большой объем данных, много миллионов строк, и убедительно доказывает, что ClickHouse справляется лучше PostgreSQL. Я же покажу как можно сократить объем данных в 49.9 раз при этом: 1. Сохранить значения локальных экстремумов 2. Отклонения от реальных значений имеют наперед заданную допустимую погрешность.

1 стартмани

30.01.2024    1754    stopa85    12    

33

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

Математика и алгоритмы Бесплатно (free)

Разработка алгоритма, построенного на модели симплекс-метода, для нахождения оптимального раскроя.

19.10.2023    4421    user1959478    50    

34

Регулярные выражения на 1С

Математика и алгоритмы Инструментарий разработчика Платформа 1С v8.3 Мобильная платформа Россия Абонемент ($m)

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

1 стартмани

09.06.2023    7464    4    SpaceOfMyHead    17    

56

Модель распределения суммы по базе

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

Обычно под распределением понимают определение сумм пропорционально коэффициентам. Предлагаю включить сюда также распределение по порядку (FIFO, LIFO) и повысить уровень размерности до 2-х. 1-ое означает, что распределение может быть не только пропорциональным, но и по порядку, а 2-ое - это вариант реализации матричного распределения: по строкам и столбцам. Возможно вас заинтересует также необычное решение этой задачи через создание DSL на базе реализации текучего интерфейса

1 стартмани

21.03.2022    7855    7    kalyaka    11    

44

Изменения формата файлов конфигурации (CF) в 8.3.16

Математика и алгоритмы Платформа 1С v8.3 Бесплатно (free)

Дополнение по формату файлов конфигурации (*.cf) в версии 8.3.16.

16.12.2021    4446    fishca    13    

36

Интересная задача на Yandex cup 2021

Математика и алгоритмы Бесплатно (free)

Мое решение задачи на Yandex cup 2021 (frontend). Лабиринт. JavaScript.

12.10.2021    8840    John_d    73    

46

Механизм анализа данных. Кластеризация.

Математика и алгоритмы Анализ учета Платформа 1С v8.3 Анализ и прогнозирование Бесплатно (free)

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

31.08.2021    7806    dusha0020    8    

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

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



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

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

17. ADirks 186 05.12.14 07:44 Сейчас в теме
А ещё есть такая интересная штука, как теория формальных языков. Её в институтах изучают.
Также есть и практические инструменты, использующие эту теорию. Например классика - yacc и bison - для C/C++. Или GOLD Parser для всего остального.
18. so-quest 140 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 7136 05.12.14 13:37 Сейчас в теме
(18) да, в моей грамматике есть ряд кривых моментов, которые появились для обхода reduce/deduce конфликтов. Как говорится красиво сделал до туда, до куда осилил, а дальше залепил бреши. Самое главное, что на основе этой грамматике сейчас работает самый продвинутый из виденных мной конструктор запросов.
24. so-quest 140 05.12.14 13:51 Сейчас в теме
(23) Он не только самый продвинутый, но и единственный которым можно пользоваться. Тебе за него - отдельный респект и уважуха. В принципе если бы не проблемы с комментариями и определением ид это или имя функции - она бы и мне подошла. Но не срослось. Сергей, если обидел тебя своим высказыванием про "плакал" - прошу прощения, но высказывание относилось не к твоему труду, а к gold parser'у - (как к неособо дружелюбной системе разроботки), и КС грамматикам в принципе (перестал их любить)
(21) Нет, в код 1С (текст)
(20) Возьми ты ту же грамматика для языка запросов и попробуй встроить комментарий везде где он может быть - если делать будешь это в GP - то там тебе даже подсветят где происходит конфликт. И подробно разжуют почему. Вообще комментарии в грамматике - отдельная тема.
29. tormozit 7136 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 7136 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 140 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 7136 05.12.14 18:39 Сейчас в теме
(27) AlexanderKai,
В моей текущей версии парсера комментарии легко обрабатываются.
Грамматика языка запросов 1с в несколько раз сложнее грамматики встроенного языка 1с и соответственно неоднозначных переходов к правилам при встрече лексемы комментария будет намного меньше.
43. orefkov 1152 26.12.14 12:10 Сейчас в теме
(26)
А зачем комментарии встраивать в грамматику? Может я что-то не понимаю? Но обычно комментарии еще на этапе лексического разбора убираются.
void ModuleParser::nextLexem()
{
	for(;;)
	{
		m_source.nextLexemWithKeyword(lastLexem);
		if(lexRemark == lastLexem.type)
			continue;
...

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

Я бы все-таки и для этой задачи не вносил комментарии в грамматику. А сделал бы комментарий просто атрибутом узла AST. На этапе лексического разбора комментарий бы просто привязывал к ближайшей последующей/предыдущей значащей лексеме. В-принципе, при желании, после построения дерева (уже после парсера грамматики) можно было бы по нему пройтись и выделить комментарии в отдельные узлы. Но так и так - это уже задача семантики, а не грамматики - следующее звено цепочки "лексика-грамматика-семантика". Грамматика - не серебрянная пуля, и не надо стараться только на ней одной выехать.
Для того же телепата и снегопата ряд моментов мной был вынесен из грамматики на уровень семантики - ибо на уровне грамматики они решались сложно, а на уровне семантики - легко. К примеру "Возврат Выражение" - то, что это допустимо только в функции, но не в процедуре, что Прервать/Продолжить допустимо только в циклах, те же ключевые слова как имена свойств/методов - это уже делалось либо пост-парсером, либо хаком разбора.
46. tormozit 7136 26.12.14 13:25 Сейчас в теме
(45) +1
В (29) я этой проблемы касался относительно комментариев. Была бы возможность (в COM-реализации парсера GoldParser), я бы ни за что не стал осквернять грамматику комментариями. Но кроме бесспорных минусов у варианта обозначения комментариев в грамматике есть и свои плюсы, которые конечно же не смогут перевесить минусы.
47. so-quest 140 27.12.14 18:16 Сейчас в теме
(46) tormozit, Была бы возможность (в COM-реализации парсера GoldParser), я бы ни за что не стал осквернять грамматику комментариями. - там же вроде в зависимости от того что вернет Parse() можно определить токен это или комментарий. могу ошибаться - сто лет туда не смотрел
48. tormozit 7136 27.12.14 21:54 Сейчас в теме
(47) определять что лексер вернул комментарий, можно, но прикреплять к узлам синтаксического дерева эти комментарии напрямую нельзя. Дерево все полностью только для чтения.
67. tormozit 7136 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 1152 08.07.20 13:27 Сейчас в теме
(67)
27.12.14 - 08.07.20
Старая гвардия просыпаться начала :)
70. so-quest 140 08.07.20 14:12 Сейчас в теме
28. so-quest 140 05.12.14 15:52 Сейчас в теме
Другими словами - ты пишешь то же самое что и я - нисходящий разбор. Интересно посмотреть - как без баловства с грамматиками можно что-то сделать.
> До запроса еще не дошел, но не думаю, что там возникнут проблемы.
При ручном разборе - нет. Не возникнут. Как впрочем и проблем с "|" не будет - у тебя оператор выбора - упорядоченный получается.
> Возможно это архитектурные ограничения Голден Парсера и там действительно это проблема.
Нет. В GP - не проблема, в общем случае для КС это сложно.
31. so-quest 140 05.12.14 19:09 Сейчас в теме
> Мне даже кажется, что его там в принципе нельзя сделать красивым.
В той постановке задачи которую принял ты - да, лучше чем у тебя сделано - никак. Повторюсь - это проблема всех КС грамматик - прочитан символ и надо определиться куда дальше идти - если сверток больше 2 - конфликт. В PEG этого нет, так как оператор альтернативы - упорядочен. Кстати, если упростить твою грамматику до одного id, а выяснение роли этого идентификатора (идентификатор ил имя функции) вынести в интерпретатор - то грамматика упрощается.


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


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

Под сложностью грамматики имею ввиду некую функцию от количества правил и средней степени вложений правил. Мне кажется грамматика встроенного языка тут явно проще будет.
33. alexinzaz 08.12.14 14:56 Сейчас в теме
Мне чет институтские лекции по основам трансляторов вспомнились:) Автору респект.
34. so-quest 140 15.12.14 21:06 Сейчас в теме
Ищу соавтора на вторую часть статьи. Будут описано построение парсера для арифметических выражений, построение генератора парсера и в качестве примера использования - описана грамматика markdown-разметки для комментариев в коде. Желающие - отметьтесь в личке.
35. brr 182 19.12.14 15:58 Сейчас в теме
(34) не могу написать вам личное сообщение, потому что у меня менее 1 стратмани :))). Готов поучаствовать.
36. orefkov 1152 25.12.14 11:30 Сейчас в теме
В снегопате процедура парсинга языка 1С состоит из 3 тысяч строк, в которых 1300 "goto" и ни одного "for" :)
37. so-quest 140 25.12.14 11:51 Сейчас в теме
38. orefkov 1152 25.12.14 11:52 Сейчас в теме
(37)
Нет конечно. Сначала bison, а потом мой скрипт по его результатам.
39. so-quest 140 25.12.14 11:54 Сейчас в теме
Косяк с х.Возврат обошел так же как и в 77?
40. orefkov 1152 25.12.14 12:34 Сейчас в теме
(39)
Немного недопонял, какой именно косяк и как я его обошел в 77?
41. so-quest 140 25.12.14 12:40 Сейчас в теме
Использование ключевых слов в имени полей. В грамматике для 77 ты ввел отдельное правило, а здесь как сделал?
42. orefkov 1152 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 160 30.12.14 06:02 Сейчас в теме
Как использовать эти алгоритмы?
Это вот что?
Проверим работоспособность парсера последовательности на следующей грамматике
‹тест1› ::= ‹м› ‹а› ‹м› ‹а›
‹м› ::= м
‹а› ::= а

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

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

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

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

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



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

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

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

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

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

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

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

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

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

будет думаю более читаемым (но опять же - для этого придется взять мой правленый xUnit, с поддержкой этой функции).
>>> Мне интересно как из строки "128 + -65" получить число 63.
Пишу, вот честно - пишу продолжение, но сейчас увы времени мало. Да и отзывов на бета версию второй части тоже немного, что не добавляет оптимизма. :)
59. dmpas 417 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 140 08.12.16 23:14 Сейчас в теме
Спасибо за комментарии. В следующей версии будет все поправлено.
63. kote 536 12.03.17 19:29 Сейчас в теме
64. tsukanov 13.08.17 16:28 Сейчас в теме
Фигасе ты заморочился )))
65. so-quest 140 14.08.17 08:55 Сейчас в теме
Давно это было. Сейчас бы делал совсем по другому.
66. Darklight 32 08.07.20 09:41 Сейчас в теме
Прошло более 4-х с половиной лет - а продолжение так и не последовало - зато статья всплыла из-загашников инфостарта....
69. so-quest 140 08.07.20 14:04 Сейчас в теме
ИМХО - не нужно это никому. Особо большого толка в парсере запросов нет. Да и этот peg-движок не без греха. В общем разработку можно считать мертвой. Доделывать точно не буду в этом стиле.
Оставьте свое сообщение