Включение грамматик (kwtypes)

Задача выделения дат является очень распространенной при извлечении фактов из текста. Чтобы не писать одни и те же правила каждый раз, мы можем включать уже написанную грамматику в другие грамматики.

Допустим, в стишке про воробья у нас не только сказано, у кого он обедал, но и уточняется, когда:

(13) — В зоопарке у зверей.

— Где обедал, воробей?

Пообедал я сперва

За решеткою у льва.

Подкрепился у лисицы 14 августа.

1 сентября у моржа попил водицы.

Ел морковку у слона 29 декабря 2011 года.

С журавлем поел пшена.

и т.д.

Мы помним, что у нас уже есть грамматика, извлекающая точные даты, и грамматика, извлекающая информацию о том, у кого обедал воробей. Чтобы выяснить, у кого и когда обедал воробей, достаточно включить одну грамматику в другую. Это можно делать двумя способами:

  1. С помощью директивы include.

    #encoding "utf-8"
    #include <date.cxx>
    Animal -> Noun<kwtype=animal>; 
    WithWho -> 'у' (Adj<gnc-agr[1]>) Animal<gram='род', rt, gnc-agr[1]> interp (Sparrow.Who);
    WithWho -> 'с' (Adj<gnc-agr[1]>) Animal<gram='твор', rt, gnc-agr[1]> interp (Sparrow.Who);
    S -> Date interp (Sparrow.When) WithWho;
    S -> WithWho Date interp (Sparrow.When);

    Директива include включает текст из указанного файла вместо самой себя. Т.е. парсер «увидит» вот такое текст:

    #encoding "utf-8"
    // грамматика извлечения дат из date.cxx
    DayOfWeek -> Noun<kwtype="день_недели">;
    Day -> AnyWord<wff=/([1-2]?[0-9])|(3[0-1])/>;
    Month -> Noun<kwtype="месяц">;
    YearDescr -> "год" | "г. ";
    Year -> AnyWord<wff=/[1-2]?[0-9]{1,3}г?\.?/>;
    Year -> Year YearDescr;
    Date -> DayOfWeek interp (Date.DayOfWeek) (Comma) Day interp (Date.Day) Month interp (Date.Month) (Year interp (Date.Year)); 
    Date -> Day interp (Date.Day) Month interp (Date.Month) (Year interp (Date.Year));
    Date -> Month interp (Date.Month) Year interp (Date.Year);
    //основная грамматика
    Animal -> Noun<kwtype=animal>; 
    WithWho -> 'у' (Adj<gnc-agr[1]>) Animal<gram='род', rt, gnc-agr[1]> interp (Sparrow.Who);
    WithWho -> 'с' (Adj<gnc-agr[1]>) Animal<gram='твор', rt, gnc-agr[1]> interp (Sparrow.Who);
    S -> Date interp (Sparrow.When) WithWho;
    S -> WithWho Date interp (Sparrow.When);

    Таким образом, директива include равносильна тому, что мы скопировали текст одной грамматики и вставили его в другую.

  2. С помощью kwtype’ов газеттира.

    Мы помним, что каждой грамматике соответствует статья в корневом словаре. По сути, такая статья содержит в себе все цепочки, которые собирает данная грамматика, а значит, использовать одну грамматику в другой можно с помощью уже известной нам пометы kwtype. Выглядеть это будет так:

    #encoding "utf-8"
    Date -> AnyWord<kwtype='**дата**'>;  // используем статью "дата" из словаря
    Animal -> Noun<kwtype=animal>; 
    WithWho -> 'у' (Adj<gnc-agr[1]>)
               Animal<gram='род', rt, gnc-agr[1]> interp (Sparrow.Who);
    WithWho -> 'с' (Adj<gnc-agr[1]>)
               Animal<gram='твор', rt, gnc-agr[1]> interp (Sparrow.Who);
    S -> Date interp (Sparrow.When) WithWho;
    S -> WithWho Date interp (Sparrow.When);

    В этом примере нетерминал Date соответствует любой цепочке, собираемой грамматикой date.cxx (которая в корневом словаре лежит в статье “дата”), например, 20 августа 2012 года.

Результат в первом и втором случае будет одинаковым.

Разница между первым и вторым способом включения грамматик заключается в том, что во втором случае сначала собираются цепочки, обозначенные kwtype’ами, а уже потом — цепочки, описываемые в правилах самой грамматики. Это означает, что во втором случае грамматика получает на вход следующий текст:

(14) — Где обедал, воробей?

— В зоопарке у зверей.

Пообедал я сперва

За решеткою у льва.

Подкрепился у лисицы 14_августа.

1_сентября у моржа попил водицы.

Ел морковку у слона 29_декабря_2011_года.

С журавлем поел пшена.

14_августа, 1_сентября и 29_декабря_2011_года – это теперь неделимые сущности, которые функционируют в тексте как одно слово. Грамматика уже не различает отдельные слова в их составе. Такие единицы наследует морфологические характеристики главного слова цепочки, из которой они были собраны.

Такое положение дел верно не только для kwtype’ов, за которыми стоят грамматики, но и для всех прочих. Например, если kwtype ссылается на статью с многословными ключами, то сначала эти многословные ключи объединятся в неделимое целое, а потом начнут работать правила грамматики. Так, если у нас есть такая статья в словаре:

animal "слон"
{
    key = "африканский слон" 
}

А на вход дана следующая строчка:

(15) Ел морковку у африканского слона 29 декабря 2011 года.

То после применения kwtype’ов наша грамматика получит следующий текст:

(16) Ел морковку у африканского_слона 29_декабря_2011_года.

И именно его парсер будет обрабатывать правилами.

Исходные файлы проекта tutorial5

tutorial5/config.proto Конфигурационный файл парсера
tutorial5/kwtypes.proto Объявление kw-типа animals
tutorial5/facttypes.proto Описание типов фактов
tutorial5/mydic.gzt Корневой словарь
tutorial5/animals_dict.gzt Словарь с названиями животных
tutorial5/mammals.txt Список млекопитающих
tutorial5/main.cxx Основная грамматика
tutorial5/date.cxx Грамматика для дат
tutorial5/test.txt Текст «Где обедал воробей ...» с датами
tutorial5/config.proto Конфигурационный файл парсера
tutorial5/kwtypes.proto Объявление kw-типа animals
tutorial5/facttypes.proto Описание типов фактов
tutorial5/mydic.gzt Корневой словарь
tutorial5/animals_dict.gzt Словарь с названиями животных
tutorial5/mammals.txt Список млекопитающих
tutorial5/main.cxx Основная грамматика
tutorial5/date.cxx Грамматика для дат
tutorial5/test.txt Текст «Где обедал воробей ...» с датами