O lado sombrio da força é um caminho para muitas habilidades, alguns considerados antinaturais.
- Darth Sidious
Com base em examples/demo.c :
| Manipulação da lista de tempo de compilação |
// 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 )))),
}; |
| Recursão 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 )); |
| Sobrecarregando em vários 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.
} |
(Dica: v(something) avalia para something .)
O Metalang99 é uma base firme para escrever metaprogramas confiáveis e sustentáveis no C99 puro. É implementado como uma linguagem FP interpretada no topo das macros pré -processador: apenas #include <metalang99.h> e você está pronto para ir. O MetalAng99 apresenta tipos de dados algébricos, correspondência de padrões, recursão, currying e coleções; Além disso, fornece meios para relatórios e depuração de erros em tempo de compilação. Com o nosso verificador de sintaxe embutido, os erros de macro devem ser perfeitamente compreensíveis, permitindo que você seja um desenvolvimento conveniente.
Atualmente, o Metalang99 é usado no OpenIPC como uma dependência indireta do Datatype99 e da Interface99; Isso inclui uma implementação RTSP 1.0, juntamente com ~ 50k linhas de código privado.
As macros facilitam a reutilização do código, as macros são o material de construção que permite moldar o idioma para se adequar ao problema que está sendo resolvido, levando a um código mais limpo e conciso. No entanto, a metaprogramação em C é totalmente castrada: não podemos nem operar com fluxo de controle, números inteiros, sequências ilimitadas e estruturas de dados compostas, jogando assim muitos metogramas hipoteticamente úteis fora do escopo.
Para resolver o problema, implementei o Metalang99. Tendo sua funcionalidade à nossa disposição, torna-se possível desenvolver metaprogramas bastante não triviais, como o 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 ));
}Diferentemente das técnicas vagas, como sindicatos marcados ou tabelas de métodos virtuais, os metogramas acima alavancam a segurança do tipo, a concisão de sintaxe e mantêm o layout exato da memória do código gerado.
Parece interessante? Confira o post motivacional para obter mais informações.
O Metalang99 é apenas um conjunto de arquivos de cabeçalho e nada mais. Para usá -lo como uma dependência, você precisa:
metalang99/include para incluir diretórios.-ftrack-macro-expansion=0 (GCC) ou -fmacro-backtrace-limit=1 (clang) para evitar erros de expansão de macro inúteis. Se você usar o cmake, a maneira recomendada é 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, você pode pré -compilar cabeçalhos em seu projeto que dependem do Metalang99. Isso diminuirá o tempo de compilação porque os cabeçalhos não serão compilados cada vez que forem incluídos.
Tutorial | Exemplos | Documentação do usuário
Feliz hacking!
Recursão macro. As chamadas recursivas se comportam conforme o esperado. Em particular, para implementar a recursão, o aumento/pré-processador, basta copiar todas as funções recursivas até um determinado limite e forças para acompanhar a profundidade da recursão ou depender de sua dedução interna. Sendo um intérprete, o Metalang99 está livre de tais desvantagens.
Quase a mesma sintaxe. O Metalang99 não parece muito estranho em comparação com a ordem PP, porque a sintaxe difere insignificante do código pré -processador usual.
Aplicação parcial. Em vez de rastrear argumentos auxiliares aqui e ali (como é feito no Boost/Processor), o aplicativo parcial do Metalang99 permite capturar um ambiente aplicando valores constantes primeiro. Além disso, a aplicação parcial facilita a melhor reutilização de metafunções; Consulte ML99_const , ML99_compose , etc.
Depuração e relatórios de erro. Você pode depurar convenientemente suas macros com ML99_abort e relatar erros irrecuperáveis com ML99_fatal . O intérprete interromperá imediatamente e fará o truque. Até onde sabemos, nenhuma outra estrutura macro fornece esse mecanismo para depuração e relatórios de erros.
Meu trabalho sobre Poica, uma linguagem de programação de pesquisa implementada no Boost/Processor, me deixou insatisfeito com o resultado. As limitações fundamentais do impulso/pré -processador tornaram a base de código simplesmente inalterável; Isso inclui chamadas de macro recursivas (bloqueadas pelo pré -processador), que tornaram a depuração de um pesadelo completo, a ausência de aplicação parcial que tornou a passagem do contexto totalmente desajeitada e todos os erros que resultaram em megabytes de mensagens de erro do compilador.
Só então eu entendi que, em vez de enriquecer o pré-processador com vários mecanismos ad-hoc, devemos realmente estabelecer um paradigma claro para estruturar metogramas. Com esses pensamentos em mente, comecei a implementar o Metalang99 ...
Para encurtar a história, demorou um ano de trabalho duro para lançar a v0.1.0 e quase um ano para torná -lo estável. Como um aplicativo do mundo real do Metalang99, criei o Datatype99 exatamente da mesma forma que eu queria: a implementação é altamente declarativa, a sintaxe é bacana e a semântica é bem definida.
Finalmente, quero dizer que o Metalang99 é apenas sobre transformações de sintaxe e não sobre tarefas ligadas à CPU; O pré -processador é muito lento e limitado para esse tipo de abuso.
ML99_assertIsTuple , ML99_assertIsNat , etc. Para melhores mensagens de diagnóstico.## token-pasting dentro das macros compatíveis com Metalang99 em vez de ML99_cat ou de seus amigos, porque os argumentos serão, no entanto, totalmente expandidos.ML99_todo e seus amigos para indicar funcionalidade não implementada. Consulte CONTRIBUTING.md .
Veja ARCHITECTURE.md .
Veja idioms.md .
Consulte optimization_tips.md .
R: Metalang99 é um grande passo para diagnósticos compreensíveis do compilador. Possui um verificador de sintaxe embutido que testa todos os termos recebidos quanto à validade:
[ 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))
| ^~~~~~~~~
O Metalang99 pode até verificar se há pré -condições de macro e relatar um erro:
[ 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)))
| ^~~~~~~~~
No entanto, se você fizer algo estranho, os erros de compilação do tempo podem ficar bastante obscurados:
// 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 )Em ambos os casos, você pode tentar depurar iterativamente seu metaprograma. Pela minha experiência, 95% dos erros são compreensíveis - o Metalang99 é construído para seres humanos, não para monstros macro.
R: Consulte o capítulo "Testes, depuração e relatórios de erros" .
A: Eu uso o código VS para desenvolvimento. Ele permite sugestões pop-up de construções geradas por macro, mas, é claro, não suporta o destaque da sintaxe macro.
A: Para executar os benchmarks, execute ./scripts/bench.sh no diretório raiz.
UM:
R: Veja a postagem do blog "Qual é o objetivo do pré -processador C, na verdade?"
R: O pré -processador C/C ++ é capaz de iterar apenas até um certo limite. Para o Metalang99, esse limite é definido em termos de etapas de redução: Uma vez que uma quantidade fixa de etapas de redução for esgotada, seu metaprograma não poderá mais executar.
R: O Metalang99 é direcionado principalmente para C Pure C, e C não possui modelos. De qualquer forma, você pode encontrar a argumentação para C ++ no site do Boost/Processor.
R: Sou contra cabeçalhos amalgamados por causa do ônus da atualização. Em vez disso, você pode apenas adicionar o Metalang99 como um submódulo Git e atualizá -lo com git submodule update --remote .
A: C99/C ++ 11 e em diante.
R: Sabe -se que o Metalang99 trabalha nesses compiladores: