Comments 23
Наконец стала понятна причина проблемы странного порядка вывода сообщения при анализе AST.
Оказывается дело не в порядке применения фильтров или способе обхода дерева, а в том, что вывод в выходной поток std::cout и llvm::outs() - зависят от настроек буферизации и чтобы исключить косяки при отладке лучше вообще не пользоваться стандартными потоками вывода, используя вместо них только llvm::outs() и llvm::errs(), так как отладочные дампы dump() или dumpColor() выводятся через них.
Лисп своими руками.
В продолжение комментаторов выше: дохляк полнейший. Попытка натянуть очередную сову на очередной глобус.
Сова - С++, глобус - всеядность.
Конкретно:
Си изначально разработан для максимального попадания конструкциями в архитектуру машин фон Неймана .
С++ - лишь "синтактический сахар", облегчающий группировку полей и функций внутри Си-структуры (ключевое слово - struct): работу по составлению таблиц, которую в Си можно сделать руками - перекладывают на компилятор.
А для доменного использования (математика, лексеры/парсеры, итд.) - нужны доменные языки ( см. https://ru.wikipedia.org/wiki/Языково-ориентированное_программирование ).
Никогда не было и не будет никаких "языков общего назначения". Вот и мучают этот бедный Си.
Причем тут сова, глобус и Си?
В статье речь о синтаксическом анализе исходного текста программы с помощью описанных инструментов. И сравнение двух подходов (visitors vs. matchers) при поиске нужных данных в абстрактном синтаксическом дереве.
А уж нужен ли вам чистый С, С++ или Objevtive-С - это уже дело десятое.
Да, но фильтр мультисписка средствами C++ просто выглядит неуклюже, как его ни пиши.
Да, но фильтр мультисписка средствами C++ просто выглядит неуклюже, как его ни пиши.
С этим я полностью согласен.
Но к сожалению, тут идет выбор не на каком языке писать фильтр, а как писать фильтр на С++ с учетом используемого инструментария.
Конечно. Clang такой, какой есть, и разбор AST-дерева – маленькая его часть.
О том и речь: хочешь рабирать грамматики - бери BNF/Bison/PEG и иже с ними, хочешь анализировать AST (он же - маркированый граф) - делай соответствующий DSL с паттэрн мэтчингом и специализацией.
Си не предназначен ни для первого ни для второго, и реализация перечисленного на нём будет кривой и корявой.
PS ИМХО. :)
Вообще-то мне нужно компилировать С++ код, но с некоторыми пользовательским атрибутами и дополнительными проверками. Для этих целей нужен анализ AST с помощью плагина под clang и финальным результатом в виде объектного файла.
Во вторых, разбирать грамматики современного С++ на BNF/Bison/PEG и иже с ними, это адовый ад, который гораздо хуже, чем писать фильтр для узлов AST на С++ как в примерах из данной статьи (и это не говоря уже про то, что придется делать ненужную работу по написанию парсера, вместо того, чтобы взять уже готовый).
Значит, мы рассматриваем разные аспекты дела.
Я говорю об "академической" стороне дела в терминах научно-теоретического обоснования. Вы - о "здесь, сейчас и быстро" в терминах "лепим из того, что есть".
Такие пассажи:
class IfStmtHandler : public MatchFinder::MatchCallback ... Tool.run(newFrontendActionFactory(&Finder).get()
где уже только на изучении документации по классам и наследованию можно сломать голову, не говоря уже о будущей поддержке кода, я отношу к костыльно-ориентированному программированию.
Но, как известно из практики, оно - "наше всё" при решении "задач бизнеса". Особенно "прямо сейчас" - а другого бизнесу и не надо.
Поэтому, коль инструмент есть, проблемы - решает, - пользуйте. Я ж не отговариваю.
Просто говорю, что выглядит всё это криво и коряво, соглашаясь с предыдущим комментатором и Вами (прочтите, что сами писали выше).
Да, но фильтр мультисписка средствами C++ просто выглядит неуклюже, как его ни пиши.
...С этим я полностью согласен.
и ранее.
Я решаю именно "академический" вопрос, но с использованием имеющихся инструментов и прицелом на будущее реальное применение. Писать свой парсер для всех возможных диалектов C++, да еще и на Bison, как вы предлагаете, это просто ужасное решение с любой стороны. Хоть с точки зрения трудоемкости и сложности разработки, хоть с точки зрения последующей поддержки такого решения, хоть с точки зрения скорости получения первого результата.
Если вы называете костылем, решение использовать уже существующий парсер, то все ваши рассуждения "... как известно из практики, оно - "наше всё" при решении "задач бизнеса". Особенно "прямо сейчас" - а другого бизнесу и не надо." , наверно обычное шапозакидательство и популизм, ничего не имеющий с реальной жизнью.
Если б мне пришлось решать эту задачу, я б рассмотрел такой вариант, как выгрузить AST дерево, обработать его программой на Lisp/Scheme и загрузить обратно.
Парсить C++ бизоном, конечно, не вариант из-за темплейтов.
Выгружать ничего не хочется, а тем более потом загружать обратно. Планирую сделать все в рамка работы компилятора с загружаемым плагином.
Настроить поиск для заданных узлов AST тот еще геморрой, но все равно на порядки проще разработки собственного парсера или использования дополнительного внешнего инструмента.
1.
Писать свой парсер для всех возможных диалектов C++, да еще и на Bison...
Передёргиваете. Разница между тем, что Вы написали и "хочешь разбирать грамматики - бери BNF/Bison/PEG и иже с ними" - ясна?
Если не совсем очевидна, то я доносил мысль, что писать парсеры на Си - это плохая затея, а парсер Clang написан на Си.
Это была критика Clang-а, а не то, что Вы подумали.
2.
Если вы называете костылем, решение использовать уже существующий парсер...
Опять осечка. Давайте внимательно читать, и попробуем вместе подумать, что я называю не костылём, а "костыльно-ориентированным программированием".
Подсказка:
...главная проблема в том, что для написания этого кода нужно просмотреть довольно много документации и заголовочных файлов, чтобы выяснить, какие методы нужно вызывать и какие типы объектов они возвращают.
Для решения этой проблемы разработчики Clang-а лепят "из того, что есть" костыль в виде - "IfStmtHandler, MatchFinder, MatchCallback, newFrontendActionFactory" и прочей трухи, что не на много лучше того, что было. А Вы его вынуждены брать и лепить из него костыли дальше (ибо куда деваться?).
По-моему это оно: "костыльно-ориентированное программирование".
...шото Вы меня разочаровываете.
Проблема тут что кроме доменного языка для поиску по AST, для рефакторинга нужно написать собственно код с логикой как найденный AST менять.
И тут придется либо делать этот DSL полным по Тьюрингу, и включать в него императивные элементы, что проект и похоронит. Либо делать что-то вроде встроенного SQL, когда доменный язык вызывается из императивной программы, что обычно тоже ужасно. Либо как в статье. Все решения довольно корявые. Я не видел решений делающих это сильно чище, чем в статье.
Я не видел решений делающих это сильно чище, чем в статье.
+100
Все решения довольно корявые.
Значит, очевидно, пользуемый инструментарий не подходит к этим задачам.
И вот это, говорит о попытке создания полу-меры - недо-DSL-я:
Для упрощения использования сопоставителей при анализе AST в проекте Clang был разработан новый инструмент — clang-query
Далее, глубоко не убеждён, что анализировать/модифицировать алгоритмы на этапе парсера (ака Clang) - есть хорошо. Ибо не его это функция.
Выгрузить дерево и работать с ним специальным инструментом, как писал выше @vadimr - гораздо лучше. Собственно, оно так и работает: Clang (функция: парсинг)->выгрузка дерева (в байткод)->анализ и оптимизация алгоритма в LLVM. А уж та простроит из байткода нужные ей графы, и с учётом выбранных опций проведёт над ними оптимизацию. Всё сделано "академически", по науке.
Деревья Clang-а разве что для статистического/эвристического анализа пригодятся (но опять же в отдельном специальном модуле такого анализа). Но если вы на этом этапе хотите отлавливать некоторые узлы и ставить в них свои метки, либо что-то сливать в свои бинарники, - так ставьте. Я что, где-то запрещал это делать?! :)))
Что по DSL-лям для работы c AST-ами (то есть структурами на графах), то они были и будут. Пример:
GraphIt - A High-Performance Domain Specific Language for Graph Analytics https://graphit-lang.org/ https://github.com/GraphIt-DSL/graphit
глубоко не убеждён, что анализировать/модифицировать алгоритмы на этапе парсера (ака Clang) - есть хорошо. Ибо не его это функция.
Это не этап парсера в компиляторе, это отдельная программа, использующая результат парсера. Остальных частей компилятора там нет, а в случае рефакторинга - превращают AST дерево обратно в код.
Для чего-то монументального наверно можно и DSL прикрутить, но он должен работать точно так же - превращать AST Clang в дерево и скармливать его DSL.
Это не этап парсера в компиляторе, это отдельная программа, использующая результат парсера.
Возможно. В этом я не специалист. Поэтому делил не по программам, а по этапам. AST же, вроде, сделал парсер, но не отдал ещё компилятору. Поэтому работу с AST-ом я отнёс к этапу парсинга кода.
С другой стороны, почему бы работу с AST не вынести в отдельный этап?
Давайте условно назовём этот этап "Дерево", и даже, не просто "Этап Дерево", а как у того Льва Толстого Дубище "...с огромными своими неуклюже, несимметрично растопыренными корявыми руками и пальцами, он старым, сердитым и презрительным уродом стоял между улыбающимися березами".
Назовём этот этап "Дерево на костылях" или "Костыльное дерево". Полагаю, на этом все наши разноглася по наименованию этапов сняты.
PS Вступая в дискуссии под темами с Дубо-кодом, где ождаешь кислые мины и душные речи... весь этот (конечно, кодовый) бред, изначально думаешь, а сколько минусов это принесёт в карму. Сия принесла -2. Но и обогатила идеями.
Выводы: карма - это Бог (тот самый)! Сам Бог подсказывает нужно ли ходить по таким путям, если признаки итога проглядывают уже в коде. :)
Вы получили минусы в карму (один из них мой), не за саму дискуссию. Ведь известно, что в споре рождается истина, а за форму подачи своих мыслей и навешивание ярлыков.
Причем, я его поставил только после нескольких сообщений о том, что ваши аргументы в принципе правильные, но не относятся к данному конкретном случаю, но вы все равно продолжали упорствовать в своих утверждениях, которые самый обычный спор рано или поздно переводят на личности, вместо обсуждения изначальной темы.
У Clang инструмента AST matchers, по сравнению с подходом AST visitors, есть одно очень существенно отличие, которое в некоторых случаях может быть очень существенным в зависимости от разрабатываемого инструмента (или целей применения).
Существенная разница между ними заключается в том, что AST visitors обрабатывает все узлы AST графа последовательно (проверяет все интересующие условия для текущего узла и больше в нему не возвращается), тогда как AST matchers прогоняет все дерево множество раз для нахождения нужных сопоставлений.
Это становится очень заметно при выводе диагностический или отладочных логов в консоль или на экран и может немного сбивать с толку. Если для AST visitors дошли до конца дерева, то анализ исходного текста завершен, а в случае AST matchers сообщение из финальной части AST, может быть в самом начале диагностики, если выводящий это сообщение матчер добавлен раньше остальных.
Похоже я ошибся насчет нескольких проходов. Каждый способ анализа реализуется за один проход по AST.
Но проблема зависимых между собой состояний анализатора никуда не девается, так как на это влияет как порядок применения фильтров, так и порядок проверки результатов поиска при использовании одного класса анализатора.
Наконец стала понятна причина проблемы странного порядка вывода сообщения при анализе AST.
Оказывается дело не в порядке применения фильтров или способе обхода дерева, а в том, что вывод в выходной поток std::cout и llvm::outs() - зависят от настроек буферизации и чтобы исключить косяки при отладке лучше вообще не пользоваться стандартными потоками вывода, используя вместо них только llvm::outs() и llvm::errs(), так как отладочные дампы dump() или dumpColor() выводятся через них.
Анализ AST и рефакторинг кода в Clang