Похоже, что GCC и LLVM-Clang используют рукописные рекурсивные анализаторы спуска , а не машинно-сгенерированные, восходящие парсеры на основе Bison-Flex.
Может ли кто-нибудь здесь подтвердить, что это так? И если да, то почему основные среды компиляторов используют рукописные синтаксические анализаторы?
Обновление : интересный блог на эту тему здесь
Ответы:
Да:
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 .
источник
foo * bar;
может анализироваться либо выражение умножения (с неиспользованным результатом), либо объявление переменнойbar
с типом указатель на-foo
. Какой из них правильный, зависит от того, находится лиtypedef
forfoo
в данный момент в области видимости, что не может быть определено с помощью какого-либо предварительного просмотра. Но это просто означает, что парсеру с рекурсивным спуском нужно добавить некрасивый дополнительный механизм, чтобы справиться с этим.Есть народная теорема, которая гласит, что 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.
источник
foo * bar;
двусмысленностью?Парсер Clang - это рукописный синтаксический анализатор с рекурсивным спуском, как и несколько других коммерческих интерфейсов C и C ++ с открытым исходным кодом.
Clang использует синтаксический анализатор с рекурсивным спуском по нескольким причинам:
В целом, для компилятора C ++ это просто не имеет большого значения: часть синтаксического анализа C ++ нетривиальна, но по-прежнему является одной из самых простых частей, поэтому стоит сохранять ее простой. Семантический анализ - особенно поиск имени, инициализация, разрешение перегрузки и создание экземпляра шаблона - на несколько порядков сложнее, чем синтаксический анализ. Если вам нужны доказательства, посмотрите распределение кода и коммитов в компоненте "Sema" Clang (для семантического анализа) по сравнению с его компонентом "Parse" (для синтаксического анализа).
источник
Парсер gcc написан от руки. . Я подозреваю, что то же самое и с лязгом. Вероятно, по нескольким причинам:
Вероятно, это не синдром «изобретено не здесь», это скорее похоже на «не было ничего оптимизированного специально для того, что нам было нужно, поэтому мы написали свое».
источник
Странные ответы там!
Грамматики 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?
источник
В частности, я не думаю, что Bison может справиться с грамматикой без двусмысленного анализа некоторых вещей и выполнения второго прохода позже.
Я знаю, что Happy Haskell допускает монадические (т.е. зависящие от состояния) синтаксические анализаторы, которые могут решить конкретную проблему с синтаксисом C, но я не знаю ни одного генератора синтаксического анализатора C, который допускал бы задаваемую пользователем монаду состояния.
Теоретически исправление ошибок было бы преимуществом рукописного синтаксического анализатора, но мой опыт работы с GCC / Clang показал, что сообщения об ошибках не особенно хороши.
Что касается производительности - некоторые претензии кажутся необоснованными. Генерация большого конечного автомата с использованием генератора синтаксического анализатора должна привести к чему-то подобному,
O(n)
и я сомневаюсь, что синтаксический анализ является узким местом в большинстве инструментов.источник