Cómo modificar el código C++ de la entrada del usuario
clang root-framework (6)
Actualmente estoy escribiendo un programa que se encuentra sobre un intérprete de C ++. El usuario ingresa los comandos de C ++ en tiempo de ejecución, que luego pasan al intérprete. Para ciertos patrones, quiero reemplazar el comando dado por un formulario modificado, para que pueda proporcionar una funcionalidad adicional.
Quiero reemplazar cualquier cosa del formulario.
A->Draw(B1, B2)
con
MyFunc(A, B1, B2).
Mi primer pensamiento fueron las expresiones regulares, pero eso sería más bien propenso a errores, ya que cualquiera de A
, B1
o B2
podrían ser expresiones arbitrarias de C ++. Como estas expresiones podrían contener cadenas entre comillas o paréntesis, sería bastante difícil relacionar todos los casos con una expresión regular. Además, puede haber múltiples formas anidadas de esta expresión.
Mi siguiente pensamiento fue llamar a clang como un subproceso, usar "-dump-ast" para obtener el árbol de sintaxis abstracta, modificarlo y luego reconstruirlo en un comando para que se pase al intérprete de C ++. Sin embargo, esto requeriría realizar un seguimiento de cualquier cambio en el entorno, como incluir archivos y declaraciones de reenvío, con el fin de proporcionar suficiente información para analizar la expresión. Como el intérprete no expone esta información, tampoco parece factible.
El tercer pensamiento fue utilizar el propio análisis interno del intérprete de C ++ para convertirlo en un árbol de sintaxis abstracta y luego construir desde allí. Sin embargo, este intérprete no expone al ast de ninguna manera que pude encontrar.
¿Hay alguna sugerencia sobre cómo proceder, ya sea a lo largo de una de las rutas indicadas, o por una ruta completamente diferente?
¿Qué sucede cuando alguien se da cuenta de la función de miembro del sorteo ( auto draw = &A::Draw;
) y luego comienza a usar draw
? Es de suponer que querría que también se llame a la misma funcionalidad de dibujo mejorada en este caso. Por lo tanto, creo que podemos llegar a la conclusión de que lo que realmente desea es reemplazar la función de miembro Draw
por una función propia.
Como parece que no está en posición de modificar la clase que contiene Draw
directamente, una solución podría ser derivar su propia clase de A
y anular Draw
en allí. Entonces su problema se reduce a que sus usuarios usen su nueva clase mejorada.
Puede volver a considerar el problema de traducir automáticamente los usos de la clase A
a su nueva clase derivada, pero esto todavía parece bastante difícil sin la ayuda de una implementación completa de C ++. Tal vez haya una manera de ocultar la antigua definición de A
y presentar su reemplazo con ese nombre, mediante un uso inteligente de los archivos de encabezado, pero no puedo determinar si ese es el caso de lo que nos ha dicho.
Otra posibilidad podría ser usar algún hacker de enlace dinámico utilizando LD_PRELOAD para reemplazar la función Draw
que se llama en tiempo de ejecución.
En caso de que el usuario desee ingresar algoritmos complejos a la aplicación, lo que sugiero es integrar un lenguaje de scripting a la aplicación. Para que el usuario pueda escribir el código [función / algoritmo de manera definida] para que la aplicación pueda ejecutarlo en el intérprete y obtener los resultados finales. Ej: Python, Perl, JS, etc.
Ya que necesitas C ++ en el intérprete http://chaiscript.com/ sería una sugerencia.
Intente modificar los encabezados para suprimir el método, luego, al compilar encontrará los errores y podrá reemplazar todo el núcleo.
En la medida en que tenga un intérprete de C ++ (como raíz del CERN), creo que debe usar el compilador para interceptar todo el Draw, una forma fácil y limpia de hacerlo que es declarar en los encabezados el método Draw como privado, usando algunas definiciones.
class ItemWithDrawMehtod
{
....
public:
#ifdef CATCHTHEMETHOD
private:
#endif
void Draw(A,B);
#ifdef CATCHTHEMETHOD
public:
#endif
....
};
Luego compila como:
gcc -DCATCHTHEMETHOD=1 yourfilein.cpp
Lo que quieres es un sistema de transformación de programas . Estas son herramientas que generalmente le permiten expresar cambios al código fuente, escritos en patrones de nivel de fuente que esencialmente dicen:
if you see *this*, replace it by *that*
pero operando en árboles de sintaxis abstracta, el proceso de emparejamiento y reemplazo es mucho más confiable de lo que se obtiene con la piratería de cadenas.
Tales herramientas deben tener analizadores para el idioma de origen de interés. El lenguaje fuente que es C ++ hace esto bastante difícil.
Clang califica como; después de todo puede analizar C ++. Los objetos OP no pueden hacerlo sin todo el contexto del entorno. En la medida en que OP está escribiendo fragmentos de programa (declaraciones, etc., bien formados) en el intérprete, Clang puede [no tengo mucha experiencia con él] tener problemas para concentrarme en lo que es el fragmento (declaración ? expresión? declaración? ...). Finalmente, Clang no es realmente un PTS; sus procedimientos de modificación de árbol no son transformaciones de fuente a fuente. Eso importa por conveniencia pero no puede impedir que OP la use; Las reglas de reescritura de sintaxis de superficie son convenientes, pero siempre puede sustituir el pirateo del árbol de procedimientos con más esfuerzo. Cuando hay más de unas pocas reglas, esto comienza a importar mucho.
GCC con Melt califica de la misma manera que Clang. Tengo la impresión de que Melt hace que GCC sea, en el mejor de los casos, un poco menos intolerable para este tipo de trabajo. YMMV.
Nuestro equipo de herramientas de reingeniería de software DMS con su parte frontal completa de C ++ 14 [EDITAR Julio de 2018: C ++ 17] califica absolutamente. El DMS se ha utilizado para realizar transformaciones masivas en bases de código C ++ a gran escala.
El DMS puede analizar fragmentos arbitrarios (bien formados) de C ++ sin que se le diga de antemano cuál es la categoría de sintaxis, y devolver un AST del tipo no terminal de gramática adecuado, utilizando su maquinaria de análisis de patrones. [Puede terminar con varios análisis, por ejemplo, ambigüedades, que tendrá que decidir cómo resolverlos, consulte ¿Por qué no se puede analizar C ++ con un analizador LR (1)? para más información] Puede hacer esto sin tener que recurrir al "entorno" si está dispuesto a vivir sin expansión de macros mientras analiza, e insiste en que las directivas del preprocesador (también se analizan) están bien estructuradas con respecto al fragmento de código (#if foo {#endif no permitido) pero eso es poco probable que sea un problema real para fragmentos de código ingresados interactivamente.
Luego, DMS ofrece una biblioteca AST de procedimiento completa para manipular los árboles analizados (buscar, inspeccionar, modificar, construir, reemplazar) y luego puede regenerar el código fuente de la superficie del árbol modificado, dando texto OP para alimentar al intérprete.
Donde brilla en este caso es que OP puede escribir la mayoría de sus modificaciones directamente como reglas de sintaxis de fuente a fuente. Para su ejemplo, puede proporcionar a DMS una regla de reescritura (sin probar pero muy cerca de la derecha):
rule replace_Draw(A:primary,B1:expression,B2:expression):
primary->primary
"/A->Draw(/B1, /B2)" -- pattern
rewrites to
"MyFunc(/A, /B1, /B2)"; -- replacement
y DMS tomará cualquier AST analizado que contenga el patrón "... Dibujar ..." del lado izquierdo y reemplazará ese subárbol con el lado derecho, después de sustituir las coincidencias por A, B1 y B2. Las comillas son metaquotes y se utilizan para distinguir el texto de C ++ del texto de sintaxis de reglas; la barra invertida es un metaescape usado dentro de metaquotes para nombrar metavariables. Para obtener más detalles sobre lo que puede decir en la sintaxis de las reglas, consulte Reglas de reescritura de DMS .
Si OP proporciona un conjunto de tales reglas, se le puede pedir a DMS que aplique todo el conjunto.
Así que creo que esto funcionaría bien para OP. Es un mecanismo bastante pesado para "agregar" al paquete que desea proporcionar a un tercero; DMS y su interfaz C ++ no son programas "pequeños". Pero las máquinas modernas tienen muchos recursos, por lo que creo que es una cuestión de qué tan mal necesita OP hacer esto.
Puede haber una manera de lograr esto principalmente con expresiones regulares.
Dado que todo lo que aparece después de Dibujar (ya está formateado correctamente como parámetros, no necesita analizarlos completamente para el propósito que ha descrito).
Fundamentalmente, la parte que importa es el "SÍMBOLO-> Dibujar ("
SÍMBOLO podría ser cualquier expresión que se resuelva en un objeto que sobrecargue -> o en un puntero de un tipo que implementa Draw (...). Si reduce esto a dos casos, puede acortar el análisis.
Para el primer caso, una expresión regular simple que busca cualquier símbolo válido de C ++, algo similar a "[A-Za-z _] [A-Za-z0-9_ /.]", Junto con la expresión literal "-> Dibujar (". Esto le dará la parte que debe ser reescrita, ya que el código que sigue a esta parte ya tiene el formato de parámetros válidos de C ++.
El segundo caso es para expresiones complejas que devuelven un puntero o un objeto sobrecargado. Esto requiere un poco más de esfuerzo, pero una breve rutina de análisis para retroceder solo a través de una expresión compleja se puede escribir de manera sorprendentemente fácil, ya que no tiene que admitir bloques (los bloques en C ++ no pueden devolver objetos, ya que las definiciones lambda no llaman al ellos mismos, y los bloques de código anidados reales {...} no pueden devolver nada directamente en línea que se aplicaría aquí). Tenga en cuenta que si la expresión no termina en), entonces tiene que ser un símbolo válido en este contexto, por lo tanto, si encuentra a) simplemente haga coincidir la anidada con (y extraiga el símbolo que precede al SÍMBOLO anidado (... (.. .) ...) -> Dibujar () patrón. Esto puede ser posible con expresiones regulares, pero también debería ser bastante fácil en código normal.
Tan pronto como tenga el símbolo o la expresión, el reemplazo es trivial, yendo desde
SÍMBOLO-> Dibujar (...
a
YourFunction (SYMBOL, ...
Sin tener que lidiar con los parámetros adicionales para Draw ().
Como beneficio adicional, las llamadas de función encadenadas se analizan de forma gratuita con este modelo, ya que puede iterar de forma recursiva sobre el código como
A->Draw(B...)->Draw(C...)
La primera iteración identifica el primer A-> Draw (y vuelve a escribir toda la declaración como
YourFunction(A, B...)->Draw(C...)
que luego identifica el segundo -> Dibujar con una expresión "YourFunction (A, ...) ->" que lo precede, y lo vuelve a escribir como
YourFunction(YourFunction(A, B...), C...)
donde B ... y C ... son parámetros de C ++ bien formados, incluidas las llamadas anidadas.
Sin saber la versión de C ++ que admite su intérprete, o el tipo de código que volverá a escribir, realmente no puedo proporcionar ningún código de muestra que valga la pena.
Una forma es cargar el código de usuario como un archivo DLL, (algo así como complementos), de esta manera, no necesita compilar su aplicación real, solo se compilará el código de usuario y la aplicación lo cargará dinámicamente.