Действительно ли парсеры GCC и Clang написаны от руки?

90

Похоже, что GCC и LLVM-Clang используют рукописные рекурсивные анализаторы спуска , а не машинно-сгенерированные, восходящие парсеры на основе Bison-Flex.

Может ли кто-нибудь здесь подтвердить, что это так? И если да, то почему основные среды компиляторов используют рукописные синтаксические анализаторы?

Обновление : интересный блог на эту тему здесь

JCLL
источник
27
Почти все распространенные компиляторы используют рукописные анализаторы. Что с этим не так?
SK-logic
2
вы должны делать это (полу) вручную, если вам нужна производительность.
Гена Бушуев
15
И не только производительность - улучшенные сообщения об ошибках, возможность восстановления и т. Д.
SK-logic
А как насчет MS VisualStudio? хотя и не с открытым исходным кодом, может ли кто-нибудь из MS проверить, что они тоже используют рукописный синтаксический анализатор рекурсивного спуска?
OrenIshShalom 01
3
@GeneBushuyev, из вики GCC: «... Хотя тайминги показали ускорение на 1,5% , основные преимущества заключаются в облегчении будущих улучшений ...» это ускорение кажется довольно незначительным ...
OrenIshShalom

Ответы:

78

Да:

  • GCC когда-то использовал синтаксический анализатор yacc (bison), но в какой-то момент в серии 3.x он был заменен рукописным рекурсивным анализатором спуска: см. Http://gcc.gnu.org/wiki/New_C_Parser для ссылки на соответствующие патчи.

  • Clang также использует рукописный синтаксический анализатор рекурсивного спуска: см. Раздел «Единый унифицированный синтаксический анализатор для C, Objective C, C ++ и Objective C ++» в конце http://clang.llvm.org/features.html .

Мэтью Слэттери
источник
3
Означает ли это, что ObjC, C и C ++ имеют грамматики LL (k)?
Lindemann
47
Нет: даже C, самый простой из трех, имеет неоднозначную грамматику. Например, foo * bar;может анализироваться либо выражение умножения (с неиспользованным результатом), либо объявление переменной barс типом указатель на- foo. Какой из них правильный, зависит от того, находится ли typedeffor fooв данный момент в области видимости, что не может быть определено с помощью какого-либо предварительного просмотра. Но это просто означает, что парсеру с рекурсивным спуском нужно добавить некрасивый дополнительный механизм, чтобы справиться с этим.
Мэтью Слэттери
9
Я могу подтвердить на основании эмпирических данных, что C ++ 11, C и Objective C имеют контекстно-свободные грамматики, которые может обрабатывать синтаксический анализатор GLR.
Ира Бакстер
2
Что касается чувствительности к контексту, этот ответ не утверждает ни того, ни другого: анализ этих языков, вероятно, будет полным по Тьюрингу.
Иоаннис Филиппидис
107

Есть народная теорема, которая гласит, что C трудно разобрать, а C ++ практически невозможно.

Это неправда.

Верно то, что C и C ++ довольно сложно анализировать с помощью парсеров LALR (1) без взлома механизма синтаксического анализа и запутывания данных таблицы символов. Фактически, GCC использовал их для синтаксического анализа, используя YACC и подобные дополнительные хакерские программы, и да, это было ужасно. Теперь GCC использует рукописные синтаксические анализаторы, но все еще с хакерством таблицы символов. Ребята из Clang никогда не пытались использовать автоматические генераторы парсеров; AFAIK синтаксический анализатор Clang всегда был ручным рекурсивным спуском.

Что верно, так это то, что C и C ++ относительно легко анализировать с помощью более сильных автоматически сгенерированных парсеров, например, парсеров GLR , и вам не нужны никакие хаки. В Эльзе C ++ анализатора является одним из примеров этого. Наш C ++ Front End является еще одним (как все наши «компилятор» передние концы, РВО довольно прекрасная технология синтаксического анализа).

Наш интерфейс на C ++ не такой быстрый, как GCC, и, конечно, медленнее, чем у Эльзы; мы потратили немного сил на его тщательную настройку, потому что у нас есть другие более насущные проблемы (тем не менее, он использовался в миллионах строк кода C ++). Эльза, вероятно, медленнее, чем GCC, просто потому, что он более общий. Учитывая сегодняшнюю скорость процессора, на практике эти различия могут не иметь большого значения.

Но «настоящие компиляторы», которые широко распространены сегодня, берут свое начало в компиляторах 10-20 лет назад или более. Тогда неэффективность имела гораздо большее значение, а о парсерах GLR никто не слышал, поэтому люди делали то, что умели. Clang, конечно, более поздний, но и народные теоремы надолго сохраняют свою «убедительность».

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

Что есть правда, что получение грамматика , которая соответствует поведению вашего дружественного соседства компилятора трудно. Хотя практически все компиляторы C ++ реализуют (большую часть) исходного стандарта, они также имеют множество расширений темного угла, например, спецификации DLL в компиляторах MS и т. Д. Если у вас есть мощный механизм синтаксического анализа, вы можете потратить свое время, пытаясь получить окончательная грамматика в соответствии с реальностью, вместо того, чтобы пытаться изменить вашу грамматику в соответствии с ограничениями вашего генератора синтаксического анализатора.

ИЗМЕНИТЬ Ноябрь 2012 г .: С момента написания этого ответа мы улучшили наш интерфейс на C ++ для обработки всего C ++ 11, включая диалекты ANSI, GNU и MS. Хотя было много лишнего, нам не нужно менять наш механизм синтаксического анализа; мы только что пересмотрели правила грамматики. Мы же должны изменить семантический анализ; C ++ 11 семантически очень сложен, и эта работа сводит на нет усилия по запуску анализатора.

ИЗМЕНИТЬ Февраль 2015: ... теперь обрабатывает полный С ++ 14. (См. Получение удобочитаемого AST из кода C ++ для анализа GLR простого фрагмента кода и печально известного «самого неприятного синтаксического анализа» C ++).

ИЗМЕНИТЬ Апрель 2017 г .: Теперь обрабатывает (черновик) C ++ 17.

Ира Бакстер
источник
6
PostScript: Точно так же, как добиться соответствия грамматики тому, что на самом деле делают поставщики, получить разрешение имени и типа, соответствующее интерпретации руководства по C ++ 11 различными поставщиками, еще сложнее, потому что единственное свидетельство, которое у вас есть, - это программы, которые немного компилируются. иначе, если можно их найти. По состоянию на август 2013 года мы в значительной степени прошли это для собственно C ++ 11, но я немного отчаиваюсь в комитете по C ++, который, кажется, одержим идеей создания еще большего (и, по опыту, более запутанного) стандарта в форме C ++ 1г.
Ира Бакстер
5
Я действительно хотел бы знать: как вы справляетесь с этой foo * bar;двусмысленностью?
Мартин
14
@Martin: наш синтаксический анализатор анализирует его в обоих направлениях, создавая дерево, содержащее специальные «узлы неоднозначности», чьи дочерние элементы являются альтернативными синтаксическими анализами; дети максимально делятся своими дочерними элементами, поэтому мы получаем DAG вместо дерева. После завершения синтаксического анализа мы запускаем средство оценки грамматики атрибутов (AGE) над DAG (причудливое название для «ходить по дереву и делать что-нибудь», если вы его не знаете), который вычисляет типы всех объявленных идентификаторов. ...
Ира Бакстер
12
... Двусмысленные дочерние элементы не могут быть одинаковыми по типу; ВОЗРАСТ при обнаружении двусмысленного ребенка, который не может быть разумно типизирован, просто удаляет его. Остались хорошо типизированные дети; Таким образом, мы определили, какой синтаксический анализ "foo bar;" верно. Этот трюк работает со всеми видами сумасшедших двусмысленностей, обнаруживаемых в реальных грамматиках, которые мы создаем для реальных диалектов C ++ 11, и * полностью отделяет синтаксический анализ от семантического анализа имен. Это чистое разделение означает гораздо меньше инженерной работы (без проблем для отладки). См. Stackoverflow.com/a/1004737/120163 для более подробного обсуждения.
Ира Бакстер
3
@TimCas: На самом деле, я с вами в критике кажущейся глупости разработки синтаксиса (и семантики) языка, который настолько сложен, что его так сложно понять (да, язык C ++ здесь сильно страдает). Я бы хотел, чтобы комитеты по языковому дизайну разрабатывали синтаксис, чтобы работали более простые технологии синтаксического анализа, и явно определяли семантику языка и проверяли ее с помощью некоторых инструментов семантического анализа. Увы, мир вроде бы не такой. Итак, я считаю, что вы строите то, что должны строить настолько хорошо, насколько можете, и продолжаете жить, несмотря на неловкость.
Ира Бакстер,
31

Парсер Clang - это рукописный синтаксический анализатор с рекурсивным спуском, как и несколько других коммерческих интерфейсов C и C ++ с открытым исходным кодом.

Clang использует синтаксический анализатор с рекурсивным спуском по нескольким причинам:

  • Производительность : рукописный синтаксический анализатор позволяет нам писать быстрый синтаксический анализатор, оптимизируя горячие пути по мере необходимости, и мы всегда контролируем эту производительность. Наличие быстрого анализатора позволило использовать Clang в других инструментах разработки, где «настоящие» анализаторы обычно не используются, например, для выделения синтаксиса и завершения кода в среде IDE.
  • Диагностика и устранение ошибок : поскольку вы полностью контролируете рукописный синтаксический анализатор с рекурсивным спуском, легко добавлять особые случаи, которые обнаруживают общие проблемы и обеспечивают отличную диагностику и восстановление после ошибок (например, см. Http: //clang.llvm .org / features.html # expressivediags ) С автоматически созданными синтаксическими анализаторами вы ограничены возможностями генератора.
  • Простота : парсеры с рекурсивным спуском легко писать, понимать и отлаживать. Вам не нужно быть экспертом по синтаксическому анализу или изучать новый инструмент для расширения / улучшения синтаксического анализатора (что особенно важно для проекта с открытым исходным кодом), но вы все равно можете получить отличные результаты.

В целом, для компилятора C ++ это просто не имеет большого значения: часть синтаксического анализа C ++ нетривиальна, но по-прежнему является одной из самых простых частей, поэтому стоит сохранять ее простой. Семантический анализ - особенно поиск имени, инициализация, разрешение перегрузки и создание экземпляра шаблона - на несколько порядков сложнее, чем синтаксический анализ. Если вам нужны доказательства, посмотрите распределение кода и коммитов в компоненте "Sema" Clang (для семантического анализа) по сравнению с его компонентом "Parse" (для синтаксического анализа).

Дуг
источник
4
Да, семантический анализ намного сложнее. У нас есть около 4000 строк правил грамматики, составляющих нашу грамматику C ++ 11, и около 180 000 строк кода грамматики атрибутов для «семантического анализа» списков сомнений, приведенных выше, и еще 100 000 строк вспомогательного кода. Разбор на самом деле не проблема, хотя это достаточно сложно, если вы начнете не с той ноги.
Ира Бакстер
1
Я не уверен, что рукописные парсеры обязательно лучше для сообщения об ошибках / восстановления. Похоже, что люди вложили больше энергии в такие парсеры, чем на улучшение парсеров, создаваемых автоматическими генераторами парсеров на практике. Кажется, есть довольно хорошее исследование по этой теме; эта конкретная статья действительно привлекла мое внимание: MG Burke, 1983, Практический метод диагностики и исправления синтаксических ошибок LR и LL, докторская диссертация, факультет компьютерных наук, Нью-Йоркский университет, см. archive.org/details/practicalmethodf00burk
Ира Бакстер
1
... продолжение этой мысли: если вы хотите изменить / расширить / настроить свой ручной синтаксический анализатор, чтобы проверять особые случаи для лучшей диагностики, тогда вы должны быть готовы сделать равные инвестиции в более качественную диагностику механически сгенерированного синтаксического анализатора. Для любого специального синтаксического анализа, который вы можете закодировать для ручного, вы можете также закодировать проверку механического (и для парсеров (G) LR вы можете сделать это в значительной степени как семантические проверки редукций). В той степени, в которой это кажется неаппетитным, можно просто лениться, но это не обвинение механически сгенерированных парсеров ИМХО.
Ира Бакстер
8

Парсер gcc написан от руки. . Я подозреваю, что то же самое и с лязгом. Вероятно, по нескольким причинам:

  • Производительность : то, что вы вручную оптимизировали для вашей конкретной задачи, почти всегда будет работать лучше, чем общее решение. Абстракция обычно имеет удар по производительности
  • Сроки : по крайней мере, в случае GCC, GCC предшествует появлению множества бесплатных инструментов разработчика (вышедших в 1987 году). В то время не существовало бесплатной версии yacc и т. Д., Что, как я полагаю, было бы приоритетом для людей в FSF.

Вероятно, это не синдром «изобретено не здесь», это скорее похоже на «не было ничего оптимизированного специально для того, что нам было нужно, поэтому мы написали свое».

Рэйф Кеттлер
источник
15
Нет бесплатной версии yacc в 1987 году? Я думаю, что были бесплатные версии, когда yacc впервые был доставлен под Unix в 70-х годах. И IIRC (другой плакат , кажется , то же самое), GCC используется , чтобы иметь YACC на основе синтаксического анализа. Я слышал, что это было оправдано для улучшения отчетов об ошибках.
Ира Бакстер,
7
Я хотел бы добавить, что часто легче генерировать хорошие сообщения об ошибках из рукописного синтаксического анализатора.
Дитрих Эпп
1
Ваше мнение о сроках неверно. Раньше в GCC был синтаксический анализатор на основе YACC, но позже он был заменен рукописным синтаксическим анализатором рекурсивного спуска.
Tommy Andersen
7

Странные ответы там!

Грамматики C / C ++ не зависят от контекста. Они контекстно-зависимы из-за панели Foo *; двусмысленность. Мы должны составить список определений типов, чтобы знать, является ли Foo типом или нет.

Ира Бакстер: Я не вижу смысла в вашем GLR. Зачем строить дерево синтаксического анализа, содержащее неоднозначности. Разбор означает решение неоднозначностей, построение синтаксического дерева. Вы разрешаете эти неоднозначности за второй проход, так что это не менее уродливо. Для меня это намного уродливее ...

Yacc - это генератор парсера LR (1) (или LALR (1)), но его можно легко изменить, чтобы он зависел от контекста. И в этом нет ничего уродливого. Yacc / Bison был создан для помощи в синтаксическом анализе языка C, поэтому, вероятно, это не самый уродливый инструмент для создания синтаксического анализатора C ...

До GCC 3.x синтаксический анализатор C генерируется yacc / bison с таблицей typedefs, построенной во время синтаксического анализа. При построении таблицы typedefs "in parse" грамматика C становится локально контекстно-свободной и, более того, "локально LR (1)".

Теперь в Gcc 4.x это рекурсивный анализатор спуска. Это точно такой же синтаксический анализатор, что и в Gcc 3.x, он по-прежнему LR (1) и имеет те же правила грамматики. Разница в том, что синтаксический анализатор yacc был переписан вручную, сдвиг / уменьшение теперь скрыты в стеке вызовов, и нет "state454: if (nextsym == '(') goto state398", как в gcc 3.x yacc's parser, так что легче исправлять, обрабатывать ошибки и выводить более приятные сообщения, а также выполнять некоторые из следующих шагов компиляции во время синтаксического анализа. Ценой гораздо меньшего "легкого для чтения" кода для gcc noob.

Почему они перешли с yacc на рекурсивный спуск? Потому что совершенно необходимо избегать yacc для синтаксического анализа C ++, и потому что GCC мечтает быть многоязычным компилятором, то есть разделяющим максимум кода между разными языками, которые он может компилировать. Поэтому синтаксический анализатор C ++ и C написаны одинаково.

C ++ труднее разбирать, чем C, потому что он не является «локальным» LR (1) как C, это даже не LR (k). Посмотрите, func<4 > 2>какая функция шаблона создана с 4> 2, т.е. func<4 > 2> должна читаться как func<1>. Это определенно не LR (1). Теперь рассмотрим, func<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>. Здесь рекурсивный спуск может легко разрешить неоднозначность ценой еще нескольких вызовов функций (parse_template_parameter - неоднозначная функция синтаксического анализатора. Если parse_template_parameter (17tokens) не удалось, попробуйте еще раз parse_template_parameter (15tokens), parse_template_parameter (13tokens) ... до тех пор, пока оно работает).

Я не знаю, почему нельзя было добавить рекурсивные субграмматики yacc / bison, может быть, это будет следующий шаг в разработке парсера gcc / GNU?

воссоединяется
источник
9
«для меня это гораздо уродливее». Что я могу вам сказать, так это то, что разработка парсера производственного качества с использованием GLR и разрешения неоднозначности задержки практична с очень небольшой командой. Все другие решения, которые я видел, включали годы публичного скрежета зубов из-за обратных сальто и хаков, необходимых для того, чтобы заставить его работать с LR, рекурсивным спуском, что угодно. Вы можете постулировать множество других интересных новых технологий синтаксического анализа, но, насколько я могу судить, на данный момент это всего лишь скрежет зубов. Идеи дешевы; исполнение дорого.
Ира Бакстер
@IraBaxter: Крысы! citeseerx.ist.psu.edu/viewdoc/…
Fizz
@Fizz: Интересная статья по синтаксическому анализу Fortress, сложного языка научного программирования. Они сказали несколько важных моментов: а) классические генераторы синтаксического анализатора (LL (k), LALR (1)) не могут справиться с жесткими грамматиками, б) они пробовали GLR, у них были проблемы с масштабированием, но разработчики были неопытны, поэтому они этого не сделали. complete [это не вина GLR] и c) они использовали анализатор Packrat с обратным отслеживанием (транзакционный) и приложили много усилий, включая работу по созданию более качественных сообщений об ошибках. Что касается их примера синтаксического анализа "{| x || x ← mySet, 3 | x}", я полагаю, что GLR справится с этим отлично, и ему не нужны пробелы.
Ира Бакстер
0

Похоже, что GCC и LLVM-Clang используют рукописные рекурсивные парсеры спуска, а не машинно-генерируемые, восходящие парсеры на основе Bison-Flex.

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

Я знаю, что Happy Haskell допускает монадические (т.е. зависящие от состояния) синтаксические анализаторы, которые могут решить конкретную проблему с синтаксисом C, но я не знаю ни одного генератора синтаксического анализатора C, который допускал бы задаваемую пользователем монаду состояния.

Теоретически исправление ошибок было бы преимуществом рукописного синтаксического анализатора, но мой опыт работы с GCC / Clang показал, что сообщения об ошибках не особенно хороши.

Что касается производительности - некоторые претензии кажутся необоснованными. Генерация большого конечного автомата с использованием генератора синтаксического анализатора должна привести к чему-то подобному, O(n)и я сомневаюсь, что синтаксический анализ является узким местом в большинстве инструментов.

Ванесса Макхейл
источник
3
На этот вопрос уже есть очень качественный ответ, что вы пытаетесь добавить?
tod