El lado oscuro de la fuerza es un camino hacia muchas habilidades, algunas consideradas antinaturales.
- Darth Sidious
Basado en examples/demo.c :
| Manipulación de la lista de tiempo de compilación |
// 3, 3, 3, 3, 3
static int five_threes [] = {
ML99_LIST_EVAL_COMMA_SEP ( ML99_listReplicate ( v ( 5 ), v ( 3 ))),
};
// 5, 4, 3, 2, 1
static int from_5_to_1 [] = {
ML99_LIST_EVAL_COMMA_SEP ( ML99_listReverse ( ML99_list ( v ( 1 , 2 , 3 , 4 , 5 )))),
};
// 9, 2, 5
static int lesser_than_10 [] = {
ML99_LIST_EVAL_COMMA_SEP (
ML99_listFilter ( ML99_appl ( v ( ML99_greater ), v ( 10 )), ML99_list ( v ( 9 , 2 , 11 , 13 , 5 )))),
}; |
| Recursión macro |
#define factorial ( n ) ML99_natMatch(n, v(factorial_))
#define factorial_Z_IMPL (...) v(1)
#define factorial_S_IMPL ( n ) ML99_mul(ML99_inc(v(n)), factorial(v(n)))
ML99_ASSERT_EQ ( factorial ( v ( 4 )), v ( 24 )); |
| Sobrecargar en varios argumentos |
typedef struct {
double width , height ;
} Rect ;
#define Rect_new (...) ML99_OVERLOAD(Rect_new_, __VA_ARGS__)
#define Rect_new_1 ( x )
{ x, x }
#define Rect_new_2 ( x , y )
{ x, y }
static Rect _7x8 = Rect_new ( 7 , 8 ), _10x10 = Rect_new ( 10 );
// ... and more!
int main ( void ) {
// Yeah. All is done at compile time.
} |
(Sugerencia: v(something) evalúa something ).
Metalang99 es una base firme para escribir metaprogramas confiables y mantenibles en C99 puro. Se implementa como un lenguaje FP interpretado sobre las macros del preprocesador: solo #include <metalang99.h> y estás listo para funcionar. Metalang99 presenta tipos de datos algebraicos, coincidencia de patrones, recursión, curry y colecciones; Además, proporciona medios para informes y depuración de errores en tiempo de compilación. Con nuestro verificador de sintaxis incorporado, los errores macro deben ser perfectamente comprensibles, lo que le permite un desarrollo conveniente.
Actualmente, Metalang99 se usa en OpenIPC como una dependencia indirecta de DataType99 e Interface99; Esto incluye una implementación RTSP 1.0 junto con ~ 50k líneas de código privado.
Las macros facilitan la reutilización del código, las macros son el material de construcción que le permite dar forma al idioma para que se resuelva el problema, lo que lleva a un código más limpio y conciso. Sin embargo, la metaprogramación en C está completamente castrada: ni siquiera podemos operar con flujo de control, enteros, secuencias ilimitadas y estructuras de datos compuestos, lo que arroja muchos metaprogramas hipotéticamente útiles fuera del alcance.
Para resolver el problema, he implementado metalang99. Tener su funcionalidad a nuestra disposición, es posible desarrollar incluso metaprogramas bastante no triviales, como Datatype99:
#include <datatype99.h>
datatype (
BinaryTree ,
( Leaf , int ),
( Node , BinaryTree * , int , BinaryTree * )
);
int sum ( const BinaryTree * tree ) {
match ( * tree ) {
of ( Leaf , x ) return * x ;
of ( Node , lhs , x , rhs ) return sum ( * lhs ) + * x + sum ( * rhs );
}
return -1 ;
}O interfaz99:
#include <interface99.h>
#include <stdio.h>
#define Shape_IFACE
vfunc( int, perim, const VSelf)
vfunc(void, scale, VSelf, int factor)
interface ( Shape );
typedef struct {
int a , b ;
} Rectangle ;
int Rectangle_perim ( const VSelf ) { /* ... */ }
void Rectangle_scale ( VSelf , int factor ) { /* ... */ }
impl ( Shape , Rectangle );
typedef struct {
int a , b , c ;
} Triangle ;
int Triangle_perim ( const VSelf ) { /* ... */ }
void Triangle_scale ( VSelf , int factor ) { /* ... */ }
impl ( Shape , Triangle );
void test ( Shape shape ) {
printf ( "perim = %dn" , VCALL ( shape , perim ));
VCALL ( shape , scale , 5 );
printf ( "perim = %dn" , VCALL ( shape , perim ));
}A diferencia de las técnicas vagas, como los sindicatos etiquetados o las tablas de método virtual, las metaprogramas anteriores aprovechan la seguridad del tipo de sintaxis y la concisión de sintaxis y mantienen el diseño de memoria exacta del código generado.
¿Parece interesante? Consulte la publicación motivacional para obtener más información.
Metalang99 es solo un conjunto de archivos de encabezado y nada más. Para usarlo como dependencia, debe:
metalang99/include para incluir directorios.-ftrack-macro-expansion=0 (gcc) o -fmacro-backtrace-limit=1 (rango) para evitar errores inútiles de expansión macro. Si usa CMake, la forma recomendada es FetchContent :
include (FetchContent)
FetchContent_Declare(
metalang99
URL https://github.com/hirrolot/metalang99/archive/refs/tags/v1.2.3.tar.gz # v1.2.3
)
FetchContent_MakeAvailable(metalang99)
target_link_libraries (MyProject metalang99)
# Disable full macro expansion backtraces for Metalang99.
if (CMAKE_C_COMPILER_ID STREQUAL "Clang" )
target_compile_options (MyProject PRIVATE -fmacro-backtrace-limit=1)
elseif (CMAKE_C_COMPILER_ID STREQUAL "GNU" )
target_compile_options (MyProject PRIVATE -ftrack-macro-expansion=0)
endif ()Opcionalmente, puede precompilar encabezados en su proyecto que dependen de Metalang99. Esto disminuirá el tiempo de compilación porque los encabezados no se compilarán cada vez que se incluyan.
Tutorial | Ejemplos | Documentación del usuario
¡Feliz piratería!
Recursión macro. Las llamadas recursivas se comportan como se esperaba. En particular, para implementar la recursión, el refuerzo/preprocesador solo copia las funciones recursivas hasta un cierto límite y fuerzas para realizar un seguimiento de la profundidad de la recursión o confiar en su deducción incorporada. Al ser un intérprete, Metalang99 está libre de tales inconvenientes.
Casi la misma sintaxis. Metalang99 no parece demasiado extraño en comparación con Order PP porque la sintaxis difiere insignificantemente del código del preprocesador habitual.
Aplicación parcial. En lugar de rastrear argumentos auxiliares aquí y allá (como se hace en Boost/Preprocessor), la aplicación parcial de Metalang99 permite capturar un entorno aplicando primero valores constantes. Además de eso, la aplicación parcial facilita una mejor reutilización de metafunciones; Ver ML99_const , ML99_compose , etc.
Reportación de depuración y error. Puede depurar convenientemente sus macros con ML99_abort e informar errores irrecuperables con ML99_fatal . El intérprete se detendrá inmediatamente y hará el truco. Hasta donde sabemos, ningún otro marco macro proporciona dicho mecanismo para la depuración e informes de errores.
Mi trabajo en POICA, un lenguaje de programación de investigación implementado en Boost/Preprocessor, me ha dejado insatisfecho con el resultado. Las limitaciones fundamentales del impulso/preprocesador han hecho que la base de código sea simplemente sin mantenimiento; Estos incluyen llamadas macro recursivas (bloqueadas por el preprocesador), que han hecho que la depuración sea una pesadilla completa, la ausencia de una aplicación parcial que ha hecho que el contexto pase completamente incómodo, y cada error que resultó en megabytes de mensajes de error del compilador.
Solo entonces he entendido que en lugar de enriquecer el preprocesador con varios mecanismos ad-hoc, realmente deberíamos establecer un paradigma claro en el que estructurar metaprogramas. Con estos pensamientos en mente, comencé a implementar metalang99 ...
En pocas palabras, tardó la mitad de un año de trabajo duro en lanzar V0.1.0 y casi un año para hacerlo estable. Como una aplicación en el mundo real de Metalang99, creé Datatype99 exactamente de la misma forma que quería que fuera: la implementación es altamente declarativa, la sintaxis es ingeniosa y la semántica está bien definida.
Finalmente, quiero decir que Metalang99 se trata solo de transformaciones de sintaxis y no de tareas unidas a CPU; El preprocesador es demasiado lento y limitado para este tipo de abuso.
ML99_assertIsTuple , ML99_assertIsNat , etc. para obtener mejores mensajes de diagnóstico.## dentro de las macros que cumplen con Metalang99 en lugar de ML99_cat o sus amigos, porque los argumentos se ampliarán por completo.ML99_todo y sus amigos para indicar funcionalidad no implementada. Ver CONTRIBUTING.md .
Ver ARCHITECTURE.md .
Ver idioms.md . MD.
Ver optimization_tips.md .
R: Metalang99 es un gran paso hacia el diagnóstico de compilador comprensible. Tiene un verificador de sintaxis incorporado que prueba todos los términos entrantes para la validez:
[ playground.c ]
ML99_EVAL ( 123 )
ML99_EVAL ( x , y , z )
ML99_EVAL ( v ( Billie ) v ( Jean )) [ /bin/sh ]
$ gcc playground.c -Imetalang99/include -ftrack-macro-expansion=0
playground.c:3:1: error: static assertion failed: "invalid term `123`"
3 | ML99_EVAL(123)
| ^~~~~~~~~
playground.c:4:1: error: static assertion failed: "invalid term `x`"
4 | ML99_EVAL(x, y, z)
| ^~~~~~~~~
playground.c:5:1: error: static assertion failed: "invalid term `(0v, Billie) (0v, Jean)`, did you miss a comma?"
5 | ML99_EVAL(v(Billie) v(Jean))
| ^~~~~~~~~
Metalang99 incluso puede verificar si hay preacondiciones macro e informar un error:
[ playground.c ]
ML99_EVAL ( ML99_listHead ( ML99_nil ()))
ML99_EVAL ( ML99_unwrapLeft ( ML99_right ( v ( 123 ))))
ML99_EVAL ( ML99_div ( v ( 18 ), v ( 4 ))) [ /bin/sh ]
$ gcc playground.c -Imetalang99/include -ftrack-macro-expansion=0
playground.c:3:1: error: static assertion failed: "ML99_listHead: expected a non-empty list"
3 | ML99_EVAL(ML99_listHead(ML99_nil()))
| ^~~~~~~~~
playground.c:4:1: error: static assertion failed: "ML99_unwrapLeft: expected ML99_left but found ML99_right"
4 | ML99_EVAL(ML99_unwrapLeft(ML99_right(v(123))))
| ^~~~~~~~~
playground.c:5:1: error: static assertion failed: "ML99_div: 18 is not divisible by 4"
5 | ML99_EVAL(ML99_div(v(18), v(4)))
| ^~~~~~~~~
Sin embargo, si hace algo incómodo, los errores de tiempo de compilación pueden oscurecerse bastante:
// ML99_PRIV_REC_NEXT_ML99_PRIV_IF_0 blah(ML99_PRIV_SYNTAX_CHECKER_EMIT_ERROR, ML99_PRIV_TERM_MATCH) ((~, ~, ~) blah, ML99_PRIV_EVAL_)(ML99_PRIV_REC_STOP, (~), 0fspace, (, ), ((0end, ~), ~), ~, ~ blah)(0)()
ML99_EVAL ((~, ~, ~) blah )En cualquier caso, puede intentar depurar iterativamente su metaprograma. A partir de mi experiencia, el 95% de los errores son comprensibles: Metalang99 está construido para humanos, no para monstruos macro.
R: Consulte el capítulo "Prueba, depuración e informes de errores" .
R: Uso VS Código para el desarrollo. Permite sugerencias emergentes de construcciones generadas por macro, pero, por supuesto, no admite el resaltado de la sintaxis de la macro.
R: Para ejecutar los puntos de referencia, ejecute ./scripts/bench.sh desde el directorio raíz.
A:
R: Vea la publicación del blog "¿Cuál es el punto del preprocesador C, en realidad?"
R: El preprocesador C/C ++ es capaz de iterar solo hasta cierto límite. Para Metalang99, este límite se define en términos de pasos de reducciones: una vez que se agota una cantidad fija de pasos de reducción, su metaprograma ya no podrá ejecutarse.
R: Metalang99 se dirige principalmente a C pura, y C carece de plantillas. Pero de todos modos, puede encontrar la argumentación de C ++ en el sitio web de Boost/Preprocessor.
R: Estoy en contra de los encabezados amalgamados debido a la carga con la actualización. En su lugar, puede agregar metalang99 como un submódulo Git y actualizarlo con git submodule update --remote .
A: C99/C ++ 11 y en adelante.
R: Se sabe que Metalang99 funciona en estos compiladores: