Le côté obscur de la force est une voie vers de nombreuses capacités, certains considérés comme non naturels.
- Dark Sidious
Basé sur examples/demo.c :
| Manipulation de la liste des temps de compilation |
// 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 )))),
}; |
| Recursion 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 )); |
| Surcharge sur un certain nombre d'arguments |
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.
} |
(Astuce: v(something) évalue something .)
Metalang99 est une base ferme pour écrire des métaprogrammes fiables et maintenables en pur C99. Il est mis en œuvre comme un langage FP interprété sur le sommet de macros de préprocesseur: juste #include <metalang99.h> Et vous êtes prêt à partir. Metalang99 dispose de types de données algébriques, de correspondance de motifs, de récursivité, de curry et de collections; En outre, il fournit des moyens de signalement des erreurs et de débogage d'erreur de compilation. Avec notre vérificateur de syntaxe intégré, les erreurs de macro doivent être parfaitement compréhensibles, vous permettant de développer un développement pratique.
Actuellement, Metalang99 est utilisé sur OpenIPC comme dépendance indirecte de DataType99 et Interface99; Cela comprend une implémentation RTSP 1.0 ainsi que des lignes de code privé ~ 50k.
Les macros facilitent la réutilisation du code, les macros sont le matériau de construction qui vous permet de façonner la langue en fonction du problème, conduisant à un code plus propre et concis. Cependant, la métaprogrammation en C est complètement castrée: nous ne pouvons même pas fonctionner avec le flux de contrôle, les entiers, les séquences illimités et les structures de données composées, jetant ainsi beaucoup de métaprogrammes hypothétiquement utiles hors de la portée.
Pour résoudre le problème, j'ai implémenté Metalang99. Ayant ses fonctionnalités à notre disposition, il devient possible de développer même des métaprogrammes assez non triviaux, tels que 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 ;
}Ou interface99:
#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 ));
}Contrairement aux vagues techniques, telles que des syndicats marqués ou des tables de méthode virtuelle, les métaprogrammes ci-dessus lentent la sécurité du type de le type, la concision de la syntaxe et maintiennent la disposition exacte de la mémoire du code généré.
Ça a l'air intéressant? Consultez le post de motivation pour plus d'informations.
Metalang99 n'est qu'un ensemble de fichiers d'en-tête et rien d'autre. Pour l'utiliser comme dépendance, vous devez:
metalang99/include pour inclure les répertoires.-ftrack-macro-expansion=0 (gcc) ou -fmacro-backtrace-limit=1 (clang) pour éviter les erreurs de macro inutiles. Si vous utilisez Cmake, la manière recommandée est 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 ()Facultativement, vous pouvez précompiler les en-têtes de votre projet qui reposent sur Metalang99. Cela diminuera le temps de compilation car les en-têtes ne seront pas compilés à chaque fois qu'ils seront inclus.
Tutoriel | Exemples | Documentation utilisateur
Joyeux piratage!
Recursion macro. Les appels récursifs se comportent comme prévu. En particulier, pour mettre en œuvre la récursivité, Boost / Preprocesseur Copy Pastes toutes les fonctions récursives jusqu'à une certaine limite et force soit à garder une trace de la profondeur de la récursivité, soit s'appuyer sur leur déduction intégrée. Étant un interprète, Metalang99 est exempt de ces inconvénients.
Presque la même syntaxe. Metalang99 ne semble pas trop étranger par rapport à l'ordre PP car la syntaxe diffère de manière insignifiante du code préprocesseur habituel.
Application partielle. Au lieu de suivre les arguments auxiliaires ici et là (comme cela se fait dans Boost / Preprocesseur), l'application partielle de Metalang99 permet de capturer un environnement en appliquant d'abord des valeurs constantes. En plus de cela, l'application partielle facilite une meilleure réutilisation des métafonctions; Voir ML99_const , ML99_compose , etc.
Débogage et rapport d'erreur. Vous pouvez déboguer facilement vos macros avec ML99_abort et signaler les erreurs irrécouvrables avec ML99_fatal . L'interprète s'arrête immédiatement et fera l'affaire. À notre connaissance, aucun autre cadre macro ne fournit un tel mécanisme pour le débogage et les rapports d'erreurs.
Mon travail sur Poica, un langage de programmation de recherche mis en œuvre sur Boost / Preprocesseur, m'a laissé insatisfait du résultat. Les limites fondamentales de Boost / Preprocesseur ont rendu la base de code tout simplement irréprochable; Il s'agit notamment d'appels de macro récursifs (bloqués par le préprocesseur), qui ont rendu le débogage d'un cauchemar complet, l'absence d'application partielle qui a rendu le contexte passant tout à fait maladroit et chaque erreur qui a entraîné des mégaoctets de messages d'erreur de compilateur.
Ce n'est qu'alors que j'ai compris qu'au lieu d'enrichir le préprocesseur avec divers mécanismes ad hoc, nous devons vraiment établir un paradigme clair dans lequel structurer les métaprogrammes. Avec ces pensées à l'esprit, j'ai commencé à mettre en œuvre Metalang99 ...
Pour faire court, il a fallu une demi-année de dur labeur pour publier la V0.1.0 et près d'un an pour la rendre stable. En tant qu'application réelle de Metalang99, j'ai créé DataType99 exactement de la même forme que je voulais: l'implémentation est très déclarative, la syntaxe est nifty et la sémantique est bien définie.
Enfin, je veux dire que Metalang99 ne concerne que les transformations de syntaxe et non sur les tâches liées au processeur; Le préprocesseur est tout simplement trop lent et limité pour ce type d'abus.
ML99_assertIsTuple , ML99_assertIsNat , etc. pour de meilleurs messages de diagnostic.## à l'intérieur des macros conformes à Metalang99 au lieu de ML99_cat ou de ses amis, car les arguments seront néanmoins entièrement élargis.ML99_todo et ses amis pour indiquer des fonctionnalités non implémentées. Voir CONTRIBUTING.md .
Voir ARCHITECTURE.md .
Voir idioms.md .
Voir optimization_tips.md .
R: Metalang99 est un grand pas vers les diagnostics de compilateur compréhensibles. Il a un vérificateur de syntaxe intégré qui teste tous les termes entrants pour la validité:
[ 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 peut même vérifier les conditions préalables aux macro et signaler une erreur:
[ 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)))
| ^~~~~~~~~
Cependant, si vous faites quelque chose de maladroit, les erreurs de temps de compilation peuvent devenir assez obscurcies:
// 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 )Dans les deux cas, vous pouvez essayer de déboguer itérativement votre métaprogramme. D'après mon expérience, 95% des erreurs sont compréhensibles - Metalang99 est construit pour les humains, pas pour les monstres macro.
R: Voir le chapitre "Test, débogage et rapport d'erreur" .
R: J'utilise VS Code pour le développement. Il permet des suggestions contextuelles de constructions macro-générées, mais, bien sûr, il ne prend pas en charge la mise en évidence de la syntaxe macro.
R: Pour exécuter les repères, exécutez ./scripts/bench.sh à partir du répertoire racine.
UN:
R: Voir le billet de blog "Quel est l'intérêt du pré-incession C, en fait?"
R: Le préprocesseur C / C ++ est capable d'itérer jusqu'à une certaine limite. Pour Metalang99, cette limite est définie en termes d'étapes de réduction: une fois qu'une quantité fixe d'étapes de réduction est épuisée, votre métaprogramme ne pourra plus exécuter.
R: Metalang99 est principalement ciblant Pure C, et C manque de modèles. Mais de toute façon, vous pouvez trouver l'argumentation pour C ++ sur le site Web de Boost / Preprocesseur.
R: Je suis contre les en-têtes amalgamés en raison du fardeau avec la mise à jour. Au lieu de cela, vous pouvez simplement ajouter Metalang99 en tant que sous-module GIT et le mettre à jour avec git submodule update --remote .
R: C99 / C ++ 11 et à partir.
R: Metalang99 est connu pour fonctionner sur ces compilateurs: