Flex / Bison: нельзя использовать semantic_type - PullRequest
0 голосов
/ 09 июня 2018

Я пытаюсь создать c ++ flex / bison parser.Я использовал этот урок в качестве отправной точки и не менял конфигурации бизона / флекса.Теперь я застрял в попытке выполнить модульное тестирование лексера.

У меня в модульных тестах есть функция, которая напрямую вызывает yylex и проверяет результат:

private: static void checkIntToken(MyScanner &scanner, Compiler *comp, unsigned long expected, unsigned char size, char isUnsigned, unsigned int line, const std::string &label) {
    yy::MyParser::location_type loc;
    yy::MyParser::semantic_type semantic; // <---- is seems like the destructor of this variable causes the crash

    int type = scanner.yylex(&semantic, &loc, comp);
    Assert::equals(yy::MyParser::token::INT, type, label + "__1");

    MyIntToken* token = semantic.as<MyIntToken*>();
    Assert::equals(expected, token->value, label + "__2");
    Assert::equals(size, token->size, label + "__3");
    Assert::equals(isUnsigned, token->isUnsigned, label + "__4");
    Assert::equals(line, loc.begin.line, label + "__5");

    //execution comes to this point, and then, program crashes
}

Сообщение об ошибке:

program: ../src/__autoGenerated__/MyParser.tab.hh:190: yy::variant<32>::~variant() [S = 32]: Assertion `!yytypeid_' failed.

Я попытался следовать логике в автоматически созданных файлах зубров и извлечь из нее какой-то смысл.Но я не преуспел в этом и в конце концов сдался.Затем я искал любой совет в Интернете об этом сообщении об ошибке, но не нашел.

Местоположение, указанное в ошибке, имеет следующий код:

~variant (){
  YYASSERT (!yytypeid_);
}

РЕДАКТИРОВАТЬ: проблема исчезаеттолько если я удалю параметр

%define parse.assert

из файла зубра.Но я не уверен, что это хорошая идея ...

Как правильно получить значение токена, сгенерированного flex, для целей модульного тестирования?

1 Ответ

0 голосов
/ 09 июня 2018

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

Так называемый «вариантный» тип, предоставляемый C ++-интерфейсом Bison, не является универсальным типом варианта.,Это было преднамеренное решение, основанное на том факте, что синтаксический анализатор всегда может определить семантический тип, связанный с семантическим значением в стеке синтаксического анализатора.(Этот факт также позволяет безопасно использовать C union в синтаксическом анализаторе.) Следовательно, запись информации о типе в «варианте» будет избыточной.Так они и не делают.В этом смысле это на самом деле не дискриминационное объединение, несмотря на то, что можно ожидать от типа с именем «Вариант».

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

Поскольку это не дискриминированное объединение, его деструктор не может уничтожить текущее значение;у него нет возможности узнать, как это сделать.

Это проектное решение на самом деле упоминается в (прискорбно недостаточной) документации для типа "вариант" Bison.(Читая это, помните, что он был изначально написан до того, как существовал std::variant. В наши дни это будет std::variant, который отвергается как "избыточный", хотя также возможно, что существование std::variant могло иметьсчастливый результат пересмотра этого дизайнерского решения).В главе о типах вариантов C ++ мы читаем:

Предупреждение : мы не используем Boost.Variant по двум причинам.Во-первых, было неприемлемо требовать Boost на компьютере пользователя (т. Е. На машине, на которой будет скомпилирован сгенерированный парсер, а не на машине, на которой был запущен бизон).Во-вторых, для каждого возможного семантического значения Boost.Variant хранит не только значение, но и тег, указывающий его тип.Но синтаксический анализатор уже «знает» тип семантического значения, так что это будет дублировать информацию.

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

И это действительно так.Поэтому любое использование «варианта» зубра должно иметь определенный тип:

  • Вы можете build вариант с аргументом типа для построения.(Это единственный случай, когда вам не нужен параметр шаблона, потому что тип выводится из аргумента. Вы должны будете использовать явный параметр шаблона, только если аргумент не был точного типа; например, целое числоменьшего ранга.)
  • Вы можете получить ссылку на значение известного типа T с помощью as<T>.(Это неопределенное поведение, если значение имеет другой тип.)
  • Вы можете уничтожить значение известного типа T с помощью destroy<T>.
  • . Вы можете скопировать или переместить значение издругой вариант известного типа T с copy<T> или move<T>.(move<T> включает в себя создание и затем уничтожение T(), поэтому вы можете не захотеть делать это, если у T был дорогой конструктор по умолчанию. В целом, меня не убеждает семантика метода moveИ его имя семантически конфликтует с std::move, но снова оно пришло первым.)
  • Вы можете поменять местами значения двух вариантов, каждый из которых имеет одинаковый известный тип T с swap<T>.

Теперь сгенерированный парсер понимает все эти ограничения и всегда знает реальные типы «вариантов», которые он имеет в своем распоряжении.Но вы можете прийти и попытаться сделать что-то с одним из этих объектов таким образом, чтобы нарушить ограничение.Поскольку у объекта действительно нет никакого способа проверить ограничение, вы получите неопределенное поведение, которое, вероятно, будет иметь катастрофические последствия.

Поэтому они также реализовали опцию, которая позволяет «вариант»проверить ограничения.Неудивительно, что это состоит в добавлении дискриминатора.Но поскольку дискриминатор используется только для проверки, а не для изменения поведения, это не маленькое целое число, которое выбирает между небольшим числом известных альтернатив, а скорее указатель на std::typeid (или NULL, если вариант нено содержат значение.) (Чтобы быть справедливым, в большинстве случаев ограничения выравнивания означают, что использование указателя для этой цели не дороже, чем использование небольшого перечисления. Все то же самое ...)

Так вот чтовы сталкиваетесьВы включили утверждения с %define parse.assert;эта опция была предоставлена ​​специально, чтобы помешать вам делать то, что вы пытаетесь сделать, что позволяет деструктору объекта варианта работать до того, как значение варианта будет явно уничтожено.

Таким образом, «правильный» способ избежать проблемыдля вставки явного вызова в конце области:

   // execution comes to this point, and then, without the following
   // call, the program will fail on an assertion
   semantic.destroy<MyIntType*>();
}

При включенном утверждении синтаксического анализа вариантный объект сможет проверить, что типы, указанные в качестве параметров шаблона для semantic.as<T> и semantic.destroy<T>те же типы, что и значение, хранящееся в объекте.(Без parse.assert это тоже ваша ответственность.)


Предупреждение: мнение следует.

В случае, если кто-то читает это, я предпочитаю использоватьРеальные типы std::variant происходят от того факта, что на самом деле для семантического значения узла AST довольно часто требуется различаемое объединение.Обычное решение (в C ++) состоит в том, чтобы создать иерархию типов, которая в некоторых отношениях является полностью искусственной, и вполне возможно, что std::variant может лучше выразить семантику.

На практике я используюИнтерфейс C и моя собственная дискриминационная реализация союза.

...