Чи справді парсери GCC та Clang написані від руки?


90

Здається, GCC та LLVM-Clang використовують рукописні парсери рекурсивного спуску , а ні машинний, розроблений на основі Bison-Flex, синтаксичний аналіз знизу вгору.

Хтось тут може підтвердити, що це так? І якщо так, то чому основні фреймворки компілятора використовують рукописні парсери?

Оновлення : цікавий блог на цю тему тут


27
Майже всі основні компілятори використовують рукописні аналізатори. Що в цьому проблема?
SK-logic

2
вам потрібно зробити це (напів-) вручну, якщо вам потрібна продуктивність.
Gene Bushuyev

15
І не тільки продуктивність - кращі повідомлення про помилки, можливість відновлення тощо
SK-logic

А як щодо MS VisualStudio? хоч і не з відкритим кодом, чи може хтось із MS перевірити, що вони теж використовують рукописний парсер рекурсивного спуску?
OrenIshShalom

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)?
Ліндеманн

47
Ні: навіть С, найпростіший із трьох, має неоднозначну граматику. Наприклад, foo * bar;міг проаналізувати як вираз множення (з невикористаним результатом), чи оголошення змінної barз типом pointer-to- foo. Який із них правильний, залежить від того, чи є typedefна fooданий момент сфера дії, що не можна визначити за будь-якою кількістю пошуку. Але це просто означає, що синтаксичний аналізатор рекурсивного спуску потребує якогось потворного додаткового механізму, який впорається з цим.
Метью Слаттері

9
Я можу підтвердити з емпіричних доказів, що C ++ 11, C та Objective C мають контекстні вільні від контексту граматики, з якими може обробляти синтаксичний аналізатор GLR.
Ira Baxter

2
Щодо чутливості до контексту, ця відповідь не стверджує ні того, що синтаксичний розбір цих мов, ймовірно, повний за Тьюрінгом.
Йоанніс Філіппідіс

106

Існує народна теорема, яка говорить, що C важко проаналізувати, а C ++ по суті неможливо.

Це неправда.

Правда полягає в тому, що C та C ++ досить важко проаналізувати за допомогою синтаксичних аналізаторів LALR (1) без злому механізму синтаксичного аналізу та заплутування даних таблиці символів. GCC фактично використовував їх синтаксичний аналіз, використовуючи YACC та додаткові хакерські атаки, як це, і так, це було потворно. Зараз GCC використовує рукописні синтаксичні аналізатори, але все ще з хакерською таблицею символів. Люди Клангу ніколи не намагалися використовувати автоматизовані генератори синтаксичного аналізатора; Парсер AFAIK Clang завжди мав ручне кодування рекурсивного спуску.

Що правда, це те, що C і C ++ порівняно легко аналізувати з сильнішими автоматично створеними синтаксичними аналізаторами, наприклад, парсерами GLR , і вам не потрібні будь-які хаки. У Ельзі C ++ аналізатора є одним з прикладів цього. Наш інтерфейс C ++ інтерфейс - інший (як і всі наші інтерфейси нашого «компілятора», GLR - це чудова технологія синтаксичного аналізу).

Наш інтерфейс C ++ не такий швидкий, як GCC, і, звичайно, повільніший, ніж Elsa; ми витратили мало енергії на його ретельну настройку, оскільки у нас є інші більш актуальні проблеми (тим не менше, це було використано на мільйонах рядків коду на C ++). Ельза, швидше за все, повільніша за GCC просто тому, що є більш загальною. Враховуючи сьогоднішню швидкість процесора, ці відмінності можуть не мати великого значення на практиці.

Але "справжні компілятори", які широко розповсюджені сьогодні, сягають своїм корінням у компілятори 10 або 20 років тому чи більше. Тоді неефективність мала набагато більше значення, і ніхто не чув про парсери GLR, тож люди робили те, що вміли робити. Кленг, звичайно, нещодавніший, але тоді народні теореми довго зберігають свою "переконливість".

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

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

EDIT листопад 2012: З моменту написання цієї відповіді ми вдосконалили наш інтерфейс C ++, щоб обробляти повний C ++ 11, включаючи діалекти ANSI, GNU та MS. Незважаючи на те, що було багато зайвих речей, нам не потрібно міняти наш механізм синтаксичного аналізу; ми щойно переглянули граматичні правила. Ми ж повинні змінити семантичний аналіз; C ++ 11 семантично дуже складний, і ця робота загрожує зусиллями для запуску парсера.

EDIT лютого 2015: ... тепер обробляє повний C ++ 14. (Див. Отримання людсько зрозумілого AST із коду c ++ для розбору GLR простого біта коду та сумнозвісний "найбільш неприємний синтаксичний розбір" C ++).

EDIT квітень 2017: Тепер обробляє (чернетка) C ++ 17.


6
PostScript: Подібно до того, як граматику узгодити з тим, що насправді роблять постачальники, складніше, отримати ім’я та роздільну здатність, щоб відповідати інтерпретації посібника C ++ 11 різним постачальником, ще складніше, оскільки єдиним доказом, який ви маєте, є програми, які трохи компілюються по-іншому, якщо ви можете їх знайти. Ми в основному минули, що станом на серпень 2013 року для власне C ++ 11, але я трохи зневірився в комітеті C ++, який здається жахливим щодо створення ще більшого (і з досвіду, більш заплутаного) стандарту у формі C ++ 1р.
Ira Baxter

5
Мені б дуже хотілося знати: як ти справляєшся з цією foo * bar;двозначністю?
Мартін

14
@Martin: наш синтаксичний аналізатор аналізує його в обидві сторони , створюючи дерево, що містить спеціальні "вузли неоднозначності", діти яких є альтернативними синтаксичними аналізами; діти максимально діляться своїми дітьми, тому ми отримуємо DAG замість дерева. Після завершення синтаксичного аналізу ми запускаємо програму оцінки граматики атрибутів (AGE) через DAG (вигадливу назву "ходити по дереву і робити речі", якщо ви цього не знаєте), який обчислює типи всіх оголошених ідентифікаторів. ...
Ira Baxter

12
... Неоднозначні діти не можуть обидва відповідати типу; ВІК при виявленні двозначної дитини, яку неможливо розумно набрати, просто видаляє її. Залишились добре набрані діти; таким чином, ми визначили, який аналіз "foo bar;" правильно. Цей фокус працює для всіх видів шалених двозначностей, виявлених у реальних граматиках, які ми будуємо для реальних діалектів C ++ 11, і * повністю відокремлює синтаксичний аналіз від семантичного аналізу імен. Це чисте розділення означає набагато менше інженерних робіт (без заплутань для налагодження). Див. Stackoverflow.com/a/1004737/120163 для подальшого обговорення.
Ira Baxter

3
@TimCas: Насправді, я з вами на заперечення очевидної дурості проектування мовного синтаксису (та семантики), який настільки складний, що так важко правильно його зрозуміти (так, мова С ++ страждає тут погано). Я хотів би, щоб комітети з проектування мов розробляли синтаксис, щоб працювали простіші технології синтаксичного аналізу, і чітко визначали семантику мови та перевіряли її за допомогою деяких інструментів семантичного аналізу. На жаль, світ, схоже, не такий. Отже, я вважаю, що ти будуєш те, що повинен побудувати якнайкраще, і продовжуєш життя, незважаючи на незграбність.
Ira Baxter,

31

Синтаксичний аналізатор Кланга - це рукописний синтаксичний аналізатор рекурсивного спуску, як і декілька інших відкритих та комерційних інтерфейсів C та C ++.

Clang використовує парсер рекурсивного спуску з кількох причин:

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

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


4
Так, семантичний аналіз набагато складніший. У нас є близько 4000 рядків граматичних правил, які складаються з нашої граматики C ++ 11, і близько 180 000 рядків граматичного коду атрибутів для списків Doub "семантичного аналізу" вище, ще 100 000 рядків допоміжного коду. Розбір насправді не є проблемою, хоча це досить складно, якщо ви починаєте не з тієї ноги.
Ira Baxter

1
Я не настільки впевнений, що синтаксичні синтаксичні аналізи обов'язково кращі для звітування про помилки / відновлення. Здається, люди вкладають більше енергії в такі синтаксичні аналізатори, ніж у посилення парсерів, що виробляються автоматичними генераторами парсерів на практиці. Здається, є досить хороші дослідження з цієї теми; ця конкретна стаття справді потрапила мені в очі: М. Г. Берк, 1983 р. Практичний метод діагностики та відновлення синтаксичних помилок LR та LL, кандидатська дисертація, кафедра комп’ютерних наук Нью-Йоркського університету, див. archive.org/details/practicalmethodf00burk
Ira Baxter

1
... продовжуючи цей мислительський потяг: якщо ви готові змінити / розширити / налаштувати власноруч створений синтаксичний аналізатор, щоб перевірити наявність особливих випадків для кращої діагностики, тоді ви повинні бути готові зробити однакові інвестиції в кращі діагнози механічно створеного парсера. Для будь-якого спеціального синтаксичного аналізу, який ви можете закодувати для ручного, ви також можете закодувати перевірку для механічного (а для (G) парсерів LR ви можете зробити це як семантичні перевірки скорочень). Настільки, що здається неапетитним, людина просто ледачий, але це не обвинувачення механічно створених парсерів IMHO.
Ira Baxter

8

синтаксичний аналізатор gcc написаний від руки. . Я підозрюю те саме щодо дзвінків. Це можливо з кількох причин:

  • Продуктивність : те, що ви оптимізували вручну для свого конкретного завдання, майже завжди буде працювати ефективніше, ніж загальне рішення. Абстракція, як правило, має результативність
  • Терміни : принаймні у випадку GCC, GCC передує багатьом безкоштовним інструментам розробника (вийшов у 1987 році). На той час не було жодної безкоштовної версії yacc тощо, що, на мою думку, було б пріоритетом для людей у ​​ФСБ.

Ймовірно, це не випадок синдрому "тут не винайдено", але, скоріше, "нічого не було оптимізовано спеціально для того, що нам потрібно, тому ми написали своє".


15
Немає безкоштовної версії yacc у 1987 році? Я думаю, що були безкоштовні версії, коли yacc вперше був поставлений під Unix ще в 70-х. І IIRC (інший плакат схожий на те саме), GCC раніше мав парсер на основі YACC. Я чув, що виправданням його зміни було покращення звітування про помилки.
Ira Baxter

7
Я хотів би додати, що часто легше створювати хороші повідомлення про помилки від рукописного аналізатора.
Дітріх Епп

1
Ваша точка зору щодо часу неточна. Раніше GCC використовував синтаксичний аналізатор на основі YACC, але згодом його замінили рукописним рекурсивним парсером.
Томмі Андерсен

7

Дивні відповіді там!

Граматики C / C ++ не є контекстовими. Вони залежать від контексту завдяки панелі Foo *; неоднозначність. Ми повинні скласти список typedefs, щоб знати, є Foo типом чи ні.

Айра Бакстер: Я не бачу сенсу у вашій справі GLR. Навіщо будувати дерево синтаксичного аналізу, яке містить неоднозначності. Розбір означає вирішення двозначностей, побудова дерева синтаксису. Ви вирішуєте ці двозначності за другий прохід, тож це не менш потворно. Для мене це набагато потворніше ...

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

Поки GCC 3.x парсер C генерується yacc / bison, з таблицею typedefs, побудованою під час аналізу. Завдяки побудові таблиці typedefs типу "в синтаксичному розборі", граматика C стає локально вільною від контексту і, крім того, "локально LR (1)".

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

Чому вони перейшли з 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, рекурсивним спуском, як ви називаєте. Ви можете постулювати безліч інших крутих нових технологій синтаксичного аналізу, але, наскільки я можу зрозуміти, це просто більше скрегіт зубами на даний момент. Ідеї ​​дешеві; страта дорога.
Ira Baxter


@Fizz: Цікава стаття про синтаксичний аналіз фортеці, складного наукового мовлення з програмування Вони сказали кілька речей, на які слід звернути увагу: а) класичні генератори синтаксичного аналізатора (LL (k), LALR (1)) не можуть обробляти жорсткі граматики; б) вони спробували GLR, мали проблеми з масштабом, але розробники були недосвідченими, тому вони цього не робили завершені [це не вина ГЛР] та в) вони використали синтаксичний аналізатор Packrat для зворотного відстеження (транзакцій) і доклали до цього багато зусиль, включаючи роботу для отримання кращих повідомлень про помилки. Що стосується їхнього прикладу синтаксичного аналізу "{| x || x ← mySet, 3 | x}", я вважаю, що GLR зробить це чудово, і йому не потрібні пробіли.
Айра Бакстер,

0

Здається, GCC та LLVM-Clang використовують рукописні синтаксичні аналітики рекурсивного спуску, а не машинний, розроблений на основі Bison-Flex, синтаксичний аналіз знизу вгору.

Зокрема, Бізон я не думаю, що може впоратися з граматикою, не розібравши деякі речі неоднозначно і не зробивши другий прохід пізніше.

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

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

Щодо виконання - деякі твердження здаються необґрунтованими. Створення великого державного автомата за допомогою генератора синтаксичного аналізатора має призвести до чогось такого, O(n)і я сумніваюся, що синтаксичний аналіз є вузьким місцем у багатьох інструментах.


3
Це питання вже має дуже якісну відповідь, що ви намагаєтесь додати?
Тод
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.