Подтвердить что ты не робот

Как изменить код С++ с пользовательского ввода

В настоящее время я пишу программу, которая находится поверх интерпретатора С++. Пользователь вводит команды С++ во время выполнения, которые затем передаются в интерпретатор. Для некоторых шаблонов я хочу заменить команду, заданную модифицированной формой, чтобы я мог предоставить дополнительную функциональность.

Я хочу заменить что-нибудь из формы

A->Draw(B1, B2)

с

MyFunc(A, B1, B2).

Моя первая мысль была регулярными выражениями, но это было бы скорее подвержено ошибкам, так как любой из A, B1 или B2 мог быть произвольным выражением С++. Поскольку эти выражения сами могли содержать цитированные строки или круглые скобки, было бы довольно сложно сопоставить все случаи с регулярным выражением. Кроме того, могут быть несколько вложенных форм этого выражения

Моя следующая мысль заключалась в том, чтобы вызвать clang как подпроцесс, использовать "-dump-ast", чтобы получить абстрактное синтаксическое дерево, изменить его, а затем перестроить в команду, которая будет передана интерпретатору С++. Однако это потребует отслеживания любых изменений среды, таких как включение файлов и форвардных объявлений, чтобы дать достаточно информации для анализа выражения. Поскольку интерпретатор не раскрывает эту информацию, это также представляется неосуществимым.

Третья мысль заключалась в том, чтобы использовать собственный синтаксический анализ интерпретатора С++ для преобразования в абстрактное синтаксическое дерево, а затем строить оттуда. Тем не менее, этот интерпретатор не раскрывает аш каким-либо образом, который я смог найти.

Есть ли какие-либо предложения относительно того, как действовать, как по одному из указанных маршрутов, так и по другому маршруту?

4b9b3361

Ответ 1

Что вам нужно, это Программа Transformation System. Это инструменты, которые обычно позволяют вам выражать изменения в исходном коде, написанные на шаблонах исходного уровня, которые по существу говорят:

 if you see *this*, replace it by *that*

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

Такие инструменты должны иметь парсеры для интересующего исходного языка. Исходный язык, являющийся С++, делает это довольно сложным.

Тип Кланга квалифицируется; ведь он может разбирать С++. Объекты OP он не может этого сделать без контекста среды. В той степени что OP печатает (правильно сформированные) фрагменты программы (инструкции и т.д.) в переводчик, Clang может [у меня нет большого опыта работы с ним я] не могу сосредоточиться на том, что такое фрагмент (выражение "выражение? выражение?...?" ). Наконец, Кланг на самом деле не PTS; его процедуры модификации дерева не являются преобразованиями источника в источник. Это важно для удобства, но может не остановить использование OP; правильное правило перезаписи синтаксиса удобно, но вы всегда можете подменять процедурный взлом дерева с большим усилием. Когда существует несколько правил, это начинает иметь большое значение.

GCC with Melt вроде того, что делает Кланг. У меня создается впечатление, что Melt делает GCC в лучшем случае немного меньше невыносимый для такого рода работ. YMMV.

Наш DMS Software Reengineering Toolkit с его полным С++ 14 front end абсолютно соответствует требованиям. DMS используется для проведения массовых преобразований на больших базах кода С++.

DMS может анализировать произвольные (хорошо сформированные) фрагменты С++, не сообщая заранее, что такое категория синтаксиса, и возвращает AST для правильного грамматического нетерминального типа, используя свой механизм разбора рисунков. [Вы можете получить несколько парсов, например. двусмысленности, что вы решите, как решить проблему, см. Почему С++ не может быть проанализирован парсером LR (1)? для дальнейшего обсуждения] Он может это сделать не прибегая к "окружающей среде", если вы готовы жить без макрорасширения при разборе и настаиваете, чтобы директивы препроцессора (они тоже разбираются) хорошо структурированы относительно фрагмента кода (#if foo {#endif not allowed), но что вряд ли станет реальной проблемой для интерактивно введенных фрагментов кода.

Затем DMS предлагает полную процедурную библиотеку AST для манипулирования обработанными деревьями (поиск, проверка, изменение, сборка, замена) и затем может восстановить исходный код поверхности из модифицированного дерева, предоставив текст OP для подачи интерпретатора.

Там, где он сияет, OP может, скорее всего, написать большую часть своих изменений непосредственно в качестве синтаксических правил источника-источника. Для его Например, он может предоставить DMS правило перезаписи (непроверено, но довольно близко справа):

rule replace_Draw(A:primary,B1:expression,B2:expression):
        primary->primary
    "\A->Draw(\B1, \B2)"     -- pattern
rewrites to
    "MyFunc(\A, \B1, \B2)";  -- replacement

а DMS возьмет любой анализируемый АСТ, содержащий левую сторону шаблона "... Рисовать..." и заменит это поддерево правой рукой после замены совпадений для A, B1 и B2. Маркеры кавычек являются мета-цитатами и используются для различения текста на С++ из текста синтаксиса правила; обратная косая черта - metaescape, используемый внутри метакодов, чтобы назвать метапеременные. Подробнее о том, что вы можете сказать в синтаксисе правила, см. Правила перезаписи DMS.

Если OP предоставляет набор таких правил, DMS может попросить применить весь набор.

Итак, я думаю, что это будет нормально работать для OP. Это довольно тяжелый механизм, чтобы "добавить" к пакету, который он хочет предоставить третьему лицу; DMS и его С++ передняя часть вряд ли являются "маленькими" программами. Но тогда у современных машин есть много ресурсов, поэтому я думаю, что это вопрос о том, насколько плохо это нужно OP.

Ответ 2

Попробуйте изменить заголовки, чтобы подавить метод, затем компиляция вы найдете ошибки и сможете заменить все ядро.

Насколько у вас есть интерпретатор С++ (как CERN Root), я думаю, вы должны использовать компилятор для перехвата всего Draw, легкий и чистый способ сделать это, объявив в заголовках метод Draw как закрытый, используя некоторые определяет

 class ItemWithDrawMehtod
 {
 ....
 public:
 #ifdef CATCHTHEMETHOD
     private:
 #endif
 void Draw(A,B);
 #ifdef CATCHTHEMETHOD
     public:
 #endif
 ....
 };

Затем скомпилируйте как:

 gcc -DCATCHTHEMETHOD=1 yourfilein.cpp

Ответ 3

В случае, если пользователь хочет ввести в приложение сложные алгоритмы, я предлагаю интегрировать язык сценариев в приложение. Чтобы пользователь мог написать код [функция/алгоритм определенным образом], чтобы приложение могло выполнить его в интерпретаторе и получить окончательные результаты. Пример: Python, Perl, JS и т.д.

Поскольку вам нужен С++ в интерпретаторе http://chaiscript.com/, это будет предложение.

Ответ 4

Что происходит, когда кто-то получает функцию члена Draw (auto draw = &A::Draw;), а затем начинает использовать draw? Предположительно, вы хотите, чтобы в этом случае также была вызвана одна и та же улучшенная функция Draw-функции. Таким образом, я думаю, мы можем заключить, что вы действительно хотите заменить функцию члена draw функцией своей собственной.

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

Вы можете снова рассмотреть проблему автоматического перевода использования класса A в ваш новый производный класс, но это все еще кажется довольно сложным без использования полной реализации на С++. Возможно, есть способ скрыть старое определение A и вместо этого заменить вашу замену под этим именем, используя умное использование файлов заголовков, но я не могу определить, произошло ли это из того, что вы нам сказали.

Другая возможность может заключаться в использовании какого-либо динамического хакера-хакера с использованием LD_PRELOAD для замены функции draw, вызываемой во время выполнения.

Ответ 5

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

Так как все, что появляется после Draw (уже правильно отформатировано в качестве параметров, вам не нужно полностью разбирать их с целью, которую вы наметили.

В сущности, важная часть - это "SYMBOL- > Draw ("

SYMBOL может быть любым выражением, которое разрешает объект, который перегружает → , или указатель на тип, реализующий Draw (...). Если вы уменьшите это до двух случаев, вы можете сократить время разбора.

В первом случае простое регулярное выражение, которое ищет любой допустимый символ С++, что-то похожее на "[A-Za-z_] [A-Za-z0-9_ \.]", а также буквальное выражение "- > Draw (". Это даст вам часть, которая должна быть перезаписана, так как код, следующий за этой частью, уже отформатирован как допустимые параметры С++.

Второй случай - для сложных выражений, которые возвращают перегруженный объект или указатель. Это требует немного больших усилий, но краткая процедура синтаксического анализа для перехода назад через просто сложное выражение может быть написана удивительно легко, так как вам не нужно поддерживать блоки (блоки в С++ не могут возвращать объекты, поскольку определения лямбда не называют самим lambda, а фактические вложенные блоки кода {...} не могут возвращать что-либо непосредственно встроенное, которое будет применяться здесь). Обратите внимание, что если выражение не заканчивается), тогда он должен быть допустимым символом в этом контексте, поэтому, если вы найдете a) просто совпадающим вложенным) с (и извлеките символ, предшествующий вложенному SYMBOL (... (...)...) → Draw(). Это может быть возможно с помощью регулярных выражений, но должно быть довольно простым и в нормальном коде.

Как только у вас есть символ или выражение, замена тривиальна, начиная с

символизации > Draw (...

к

YourFunction (SYMBOL,...

без необходимости иметь дело с дополнительными параметрами Draw().

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

A->Draw(B...)->Draw(C...)

Первая итерация идентифицирует первый A- > Draw (и переписывает весь оператор как

YourFunction(A, B...)->Draw(C...)

который затем идентифицирует второй → Draw с выражением "YourFunction (A,...) → ", предшествующим ему, и переписывает его как

YourFunction(YourFunction(A, B...), C...)

где B... и C... - это хорошо сформированные параметры С++, включая вложенные вызовы.

Не зная версию С++, которую поддерживает ваш интерпретатор, или код, который вы будете переписывать, я действительно не могу предоставить какой-либо пример кода, который может быть полезен.

Ответ 6

Один из способов - загрузить код пользователя как DLL (что-то вроде плагинов) таким образом, вам не нужно компилировать ваше фактическое приложение, просто скомпилированный код пользователя, и приложение будет загружать его динамически.