Esto comenzó como una pregunta SO, pero me di cuenta de que es bastante poco convencional y, según la descripción real de los sitios web, podría ser más adecuado para los programadores, ya que la pregunta tiene mucho peso conceptual.
He estado aprendiendo LibTooling clang y es una herramienta muy poderosa capaz de exponer todo el "meollo de la cuestión" del código de una manera amigable, es decir, de una manera semántica , y no adivinando tampoco. Si clang puede compilar su código, entonces clang está seguro de la semántica de cada personaje dentro de ese código.
Ahora permíteme dar un paso atrás por un momento.
Hay muchos problemas prácticos que surgen cuando uno participa en la metaprogramación de plantillas C ++ (y especialmente cuando se aventura más allá de las plantillas en el territorio de las macros inteligentes, aunque aterradoras). Para ser honesto, para muchos programadores, incluido yo mismo, muchos de los usos comunes de las plantillas también son algo terroríficos.
Supongo que un buen ejemplo sería cadenas de tiempo de compilación . Esta es una pregunta que tiene más de un año, pero está claro que C ++ en este momento no facilita esto a los simples mortales. Si bien mirar estas opciones no es suficiente para provocarme náuseas, sin embargo, no me deja seguro de poder producir un código de máquina mágico y de máxima eficiencia para adaptarse a cualquier aplicación elegante que tenga para mi software.
Quiero decir, seamos sinceros, amigos, las cadenas son bastante simples y básicas. Algunos de nosotros solo queremos una manera conveniente de emitir código de máquina que tenga ciertas cadenas "cocidas" significativamente más de lo que obtenemos al codificarlo de manera directa. En nuestro código C ++.
Ingrese clang y LibTooling, que expone el árbol de sintaxis abstracta (AST) del código fuente y permite que una aplicación C ++ personalizada simple manipule de manera correcta y confiable el código fuente sin procesar (usando Rewriter
) junto con un rico modelo semántico orientado a objetos de todo en el AST. Maneja muchas cosas. Conoce las macro expansiones y te permite seguir esas cadenas. Sí, estoy hablando de la transformación o traducción del código fuente a fuente.
Mi tesis fundamental aquí es que el sonido metálico ahora nos permite crear ejecutables que pueden funcionar como las etapas de preprocesador personalizadas ideales para nuestro software C ++, y podemos implementar estas etapas de metaprogramación con C ++. Simplemente estamos limitados por el hecho de que esta etapa debe tomar la entrada que es un código C ++ válido y producir como salida un código C ++ más válido. Además de cualquier otra restricción que aplique su sistema de compilación.
La entrada debe estar al menos muy cerca de un código C ++ válido porque, después de todo, clang es el front-end del compilador y solo estamos hurgando y siendo creativos con su API. No sé si hay alguna disposición para poder definir una nueva sintaxis para usar, pero claramente tenemos que desarrollar las formas de analizarla correctamente y agregarla al proyecto clang para poder hacer esto. Esperar más es tener algo en el proyecto clang que está fuera de alcance.
No es un problema. Me imagino que algunas funciones macro no-op pueden manejar esta tarea.
Otra forma de ver lo que estoy describiendo es implementar construcciones de metaprogramación usando C ++ en tiempo de ejecución manipulando el AST de nuestro código fuente (gracias a clang y su API) en lugar de implementarlas usando las herramientas más limitadas disponibles en el lenguaje mismo. Esto también tiene claros beneficios en el rendimiento de la compilación (los encabezados con plantillas pesadas ralentizan la compilación proporcionalmente a la frecuencia con la que los usas. Muchas cosas compiladas son cuidadosamente emparejadas y desechadas por el enlazador).
Sin embargo, esto tiene el costo de introducir uno o dos pasos adicionales en el proceso de compilación y también el requisito de escribir un software (ciertamente) algo más detallado (pero al menos es un tiempo de ejecución C ++ sencillo) como parte de nuestra herramienta .
Esa no es la imagen completa. Estoy bastante seguro de que existe un espacio mucho más amplio de funcionalidad que se puede obtener generando código que es extremadamente difícil o imposible con las características principales del lenguaje. En C ++ puede escribir una plantilla o una macro o una combinación loca de ambos, pero en una herramienta de sonido puede modificar clases y funciones de CUALQUIER manera que pueda lograr con C ++, en tiempo de ejecución , mientras tiene acceso completo al contenido semántico, Además de plantilla y macros y todo lo demás.
Entonces, me pregunto por qué todos ya no están haciendo esto. ¿Es que esta funcionalidad de clang es tan nueva y nadie está familiarizado con la enorme jerarquía de clases de AST de clang? Eso no puede ser.
Quizás solo estoy subestimando la dificultad de esto un poco, pero hacer "manipulación de cadenas en tiempo de compilación" con una herramienta de sonido metálico es casi criminalmente simple. Es detallado, pero es increíblemente sencillo. Todo lo que se necesita es un montón de funciones macro no operativas que se correlacionen con std::string
operaciones reales reales . El complemento clang implementa esto mediante la obtención de todas las llamadas macro no-op relevantes, y realiza las operaciones con cadenas. Esta herramienta luego se inserta como parte del proceso de construcción. Durante la compilación, estas llamadas a funciones de macro no operativas se evalúan automáticamente en sus resultados y luego se vuelven a insertar como cadenas de tiempo de compilación simples en el programa. El programa se puede compilar como de costumbre. De hecho, este programa resultante también es mucho más portátil, ya que no requiere un nuevo compilador elegante que admita C ++ 11.
fuente
Respuestas:
Sí, Virginia, hay un Papá Noel.
La noción de usar programas para modificar programas ha existido desde hace mucho tiempo. La idea original surgió de John von Neumann en forma de computadoras con programas almacenados. Pero el código de máquina que modifica el código de máquina de manera arbitraria es bastante inconveniente.
La gente generalmente quiere modificar el código fuente. Esto se realiza principalmente en forma de sistemas de transformación de programas (PTS) .
PTS generalmente ofrece, al menos para un lenguaje de programación, la capacidad de analizar AST, manipular ese AST y regenerar texto fuente válido. Si de hecho cava, para la mayoría de los lenguajes convencionales, alguien ha creado una herramienta de este tipo (Clang es un ejemplo para C ++, el compilador de Java ofrece esta capacidad como API, Microsoft ofrece Rosyln, JDT de Eclipse, ...) con un procedimiento API que en realidad es bastante útil. Para la comunidad en general, casi todas las comunidades específicas del idioma pueden apuntar a algo como esto, implementado con varios niveles de madurez (generalmente modesto, muchos "analizadores que producen AST"). Feliz metaprogramación.
[Hay una comunidad orientada a la reflexión que intenta hacer metaprogramación desde el interior del lenguaje de programación, pero solo logra la modificación del comportamiento de "tiempo de ejecución", y solo en la medida en que los compiladores del lenguaje ponen a disposición cierta información por reflexión. Con la excepción de LISP, siempre hay detalles sobre el programa que no están disponibles por reflexión ("Luke, necesitas la fuente") que siempre limitan lo que la reflexión puede hacer.]
Los PTS más interesantes hacen esto para los idiomas arbitrarios (le da a la herramienta una descripción del idioma como parámetro de configuración, incluido como mínimo el BNF). Tal PTS también le permite realizar una transformación "fuente a fuente", por ejemplo, especificar patrones directamente utilizando la sintaxis de superficie del lenguaje de destino; utilizando dichos patrones, puede codificar fragmentos de interés y / o buscar y reemplazar fragmentos de código. Esto es mucho más conveniente que la API de programación, porque no tiene que conocer todos los detalles microscópicos sobre los AST para hacer la mayor parte de su trabajo. Piense en esto como meta-metaprogramación: -}
Una desventaja: a menos que el PTS ofrezca varios tipos de análisis estáticos útiles (tablas de símbolos, control y análisis de flujo de datos), es difícil escribir transformaciones realmente interesantes de esta manera, porque necesita verificar los tipos y verificar los flujos de información para la mayoría de las tareas prácticas. Desafortunadamente, esta capacidad es de hecho rara en el PTS general. (Siempre no está disponible con el siempre propuesto "Si acabo de tener un analizador ..." Vea mi biografía para una discusión más larga de "La vida después de analizar").
Hay un teorema que dice que si puede hacer una reescritura de cadenas [por lo tanto, una reescritura de árbol] puede hacer una transformación arbitraria; y, por lo tanto, una serie de PTS se apoyan en esto para afirmar que puede metaprogramar cualquier cosa con solo las reescrituras de árbol que ofrecen. Si bien el teorema es satisfactorio en el sentido de que ahora está seguro de que puede hacer cualquier cosa, no es satisfactorio de la misma manera que la capacidad de una máquina de Turing para hacer cualquier cosa no hace que la programación de una máquina de Turing sea el método de elección. (Lo mismo es cierto para los sistemas con solo API de procedimiento, si le permiten realizar cambios arbitrarios en el AST [y, de hecho, creo que esto no es cierto para Clang]).
Lo que quiere es lo mejor de ambos mundos, un sistema que le ofrece la generalidad del tipo de PTS parametrizado por idioma (incluso manejando múltiples idiomas), con análisis estáticos adicionales, la capacidad de mezclar transformaciones de fuente a fuente con procedimientos APIs. Solo sé de dos que hacen esto:
A menos que desee escribir las descripciones del lenguaje y los analizadores estáticos usted mismo (para C ++ esto es una gran cantidad de trabajo, por eso Clang fue construido como un compilador y como una base de metaprogramación de procedimiento general), querrá un PTS con descripciones de lenguaje maduro ya disponible. De lo contrario, pasará todo su tiempo configurando el PTS, y ninguno hará el trabajo que realmente quería hacer. [Si elige un lenguaje aleatorio, no convencional, este paso es muy difícil de evitar].
Rascal intenta hacer esto cooptando "OPP" (Otros analizadores de personas) pero eso no ayuda con la parte de análisis estático. Creo que tienen Java bastante bien en la mano, pero estoy muy seguro de que no hacen C o C ++. Pero, es una herramienta de investigación académica; Es difícil culparlos.
Destaco que nuestra herramienta DMS [comercial] tiene Java, C, C ++ front-end disponibles. Para C ++, cubre casi todo en C ++ 14 para GCC e incluso las variaciones de Microsoft (y estamos puliendo ahora), expansión macro y administración condicional, y control de nivel de método y análisis de flujo de datos. Y sí, puede especificar los cambios gramaticales de manera práctica; Creamos un sistema VectorC ++ personalizado para un cliente que extendió radicalmente C ++ para usar lo que equivale a las operaciones de matriz paralela de datos F90 / APL. DMS se ha utilizado para llevar a cabo otras tareas de metaprogramación masiva en grandes sistemas C ++ (por ejemplo, remodelación arquitectónica de aplicaciones). (Soy el arquitecto detrás de DMS).
Feliz meta-metaprogramación.
fuente
La metaprogramación en C ++ con la API de compiladores (en lugar de usar plantillas) es realmente interesante y prácticamente posible. Dado que la metaprogramación no está (todavía) estandarizada, estará vinculado a un compilador específico, que no es el caso con las plantillas.
Mucha gente hace esto, pero en otros idiomas. Mi opinión es que la mayoría de los desarrolladores de C ++ (o Java, o C) no ven la necesidad (tal vez con razón), o no están acostumbrados a los enfoques de metaprogramación; También creo que están contentos con las características de refactorización / generación de código de su IDE, y que cualquier cosa más elegante podría verse demasiado compleja / difícil de mantener / difícil de depurar. Sin las herramientas adecuadas, podría ser cierto. También debe tener en cuenta la inercia y otros problemas no técnicos, como contratar y / o capacitar personas.
Por cierto, dado que estamos mencionando Common Lisp y su sistema macro (ver la respuesta de Basile), debo decir que ayer mismo, Clasp fue liberado (no estoy afiliado):
Clasp pretende ser una implementación de Common Lisp conforme que compila a LLVM IR. Además, expone las bibliotecas de Clang (AST, Matcher) al desarrollador.
Primero, eso significa que podría escribir en CL y no usar más C ++, excepto cuando use sus bibliotecas (y si necesita macros, use macros CL).
En segundo lugar, puede escribir herramientas en CL para su código C ++ existente (análisis, refactorización, ...).
fuente
Varios compiladores de C ++ tienen una API más o menos documentada y estable, en particular la mayoría de los compiladores de software libre.
Clang / LLVM es principalmente un gran conjunto de bibliotecas, y podría usarlas.
GCC reciente está aceptando complementos . En particular, puede extenderlo usando MELT (que en sí mismo es un meta-complemento, que le proporciona un lenguaje específico de dominio de nivel superior para extender GCC).
Tenga en cuenta que la sintaxis de C ++ no es fácilmente extensible en GCC (y probablemente tampoco en Clang), pero puede agregar sus propios pragmas, componentes incorporados, atributos y pases de compilación para hacer lo que desee (quizás también proporcione algunas macros de preprocesador que invoquen estas cosas) para dar una sintaxis fácil de usar).
Puede que le interesen los compiladores y los lenguajes de etapas múltiples; consulte, por ejemplo, el documento Implementación de lenguajes de etapas múltiples utilizando AST, Gensym y el documento de reflexión de C.Calcagno et al. y trabajar alrededor de MetaOcaml . Sin duda, debe mirar dentro de las macro instalaciones de Common Lisp . Y usted podría estar interesado por las bibliotecas JIT como libjit , un rayo de GNU , incluso LLVM , o simplemente -al tiempo de ejecución - generar algo de código C ++, tenedor una compilación de ella en una biblioteca de objeto dinámico compartido, entonces dlopen (3) que compartían objeto. El blog de J.Pitrat también está relacionado con dichos enfoques reflexivos. Y también RefPerSys .
fuente