2013-06-16 13 views
7

Chcę wymusić preprocesor zrobić jakieś automatyczne generowanie kodu dla mnie. Nie potrzebuję wiele: po prostu prosta pętla for, która zawiera inną pętlę for. [1]Jak napisać rekurencyjne makro pętli "repeat" w celu wygenerowania kodu C za pomocą preprocesora CPP?

Czytałem wszystko, co mogę o ekspansji makra, a nie chichot, gdy niebieska farba wyjdzie. W dobry dzień mogę nawet wyjaśnić, dlaczego potrzebujemy wielu warstw makr do wygenerowania nazwy funkcji z wklejonymi tokenami. Właściwie działa pętla for. Ale jeśli chodzi o umieszczenie pętli w pętli, jestem sprowadzany do losowego traktowania przez DEFER, EVAL i OBSTRUCT i mając nadzieję na najlepsze.

nie zostanie zahamowane połączeń do rozumu. Naprawdę chcę to zrobić ze standardowym preprocesorem C. Obiecuję, że niezależnie od wyniku, ani ja, ani mój pracodawca, ani moi spadkobiercy nie pozwiemy cię za błędy technologiczne. Obiecuję, że nie pozwolę nikomu innemu zachować kodu, a nawet go obejrzeć, bez odpowiednich okularów ochronnych. Udawaj, jeśli chcesz, po prostu pytam z teoretycznego punktu widzenia. Albo, że moją jedyną opcją jest użycie M4: jeśli makra rekursywne w CPP są perwersyjne, to na pewno M4 jest całym kurczakiem.

Najlepszy materiał referencyjny Znalazłem to 9-letni wątek Usenet: http://comp.std.c.narkive.com/5WbJfCof/double-cpp-expansion

Zaczyna niezwiązanych z tematem jest nieco małostkowy i wojowniczy w tonie i jest sposobem na głowę. Ale myślę, że odpowiedź, której szukam, jest gdzieś tam.

Następnym najlepiej jest dokumentacja dla KPP nadużywa nagłówku zwanego Cloak: https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms

To zajmuje trochę inne podejście do iteracji i być może służyć zamiast moich potrzeb. Ale to także dobry przegląd.

Oto niektóre kodu cut-dół, aby pokazać, gdzie utknąłem.

repeat.h:

#define REPEAT(macro, times, start_n, next_func, next_arg, macro_args...) \ 
    _REPEAT_ ## times(macro, start_n, next_func, next_arg, ## macro_args) 

#define REPEAT_ADD_ONE(macro, times, start_n, macro_args...)     \ 
    REPEAT(macro, times, start_n, _REPEAT_ADD_ONE, 0, ## macro_args) 

#define _REPEAT_ADD_ONE(n, ignore...) _REPEAT_ADD_ONE_ ## n 

#define _REPEAT_0(args...) /* empty */ 
#define _REPEAT_1(macro, n, func, i, args...) macro(n, ## args) 
#define _REPEAT_2(m, n, f, i, a...) m(n, ## a); _REPEAT_1(m, f(n, i), f, i, ## a) 
#define _REPEAT_3(m, n, f, i, a...) m(n, ## a); _REPEAT_2(m, f(n, i), f, i, ## a) 
#define _REPEAT_4(m, n, f, i, a...) m(n, ## a); _REPEAT_3(m, f(n, i), f, i, ## a) 
#define _REPEAT_5(m, n, f, i, a...) m(n, ## a); _REPEAT_4(m, f(n, i), f, i, ## a) 
#define _REPEAT_6(m, n, f, i, a...) m(n, ## a); _REPEAT_5(m, f(n, i), f, i, ## a) 
#define _REPEAT_7(m, n, f, i, a...) m(n, ## a); _REPEAT_6(m, f(n, i), f, i, ## a) 
#define _REPEAT_8(m, n, f, i, a...) m(n, ## a); _REPEAT_7(m, f(n, i), f, i, ## a) 
#define _REPEAT_9(m, n, f, i, a...) m(n, ## a); _REPEAT_8(m, f(n, i), f, i, ## a) 
#define _REPEAT_10(m, n, f, i, a...) m(n, ## a); _REPEAT_9(m, f(n, i), f, i, ## a) 

#define _REPEAT_ADD_ONE_0 1 
#define _REPEAT_ADD_ONE_1 2 
#define _REPEAT_ADD_ONE_2 3 
#define _REPEAT_ADD_ONE_3 4 
#define _REPEAT_ADD_ONE_4 5 
#define _REPEAT_ADD_ONE_5 6 
#define _REPEAT_ADD_ONE_6 7 
#define _REPEAT_ADD_ONE_7 8 
#define _REPEAT_ADD_ONE_8 9 
#define _REPEAT_ADD_ONE_9 10 
#define _REPEAT_ADD_ONE_10 11 

#define _REPEAT_ADD_0(x) x 
#define _REPEAT_ADD_1(x) _REPEAT_ADD_ONE(x) 
#define _REPEAT_ADD_2(x) _REPEAT_ADD_1(_REPEAT_ADD_ONE(x)) 
#define _REPEAT_ADD_3(x) _REPEAT_ADD_2(_REPEAT_ADD_ONE(x)) 
#define _REPEAT_ADD_4(x) _REPEAT_ADD_3(_REPEAT_ADD_ONE(x)) 
#define _REPEAT_ADD_5(x) _REPEAT_ADD_4(_REPEAT_ADD_ONE(x)) 
#define _REPEAT_ADD_6(x) _REPEAT_ADD_5(_REPEAT_ADD_ONE(x)) 
#define _REPEAT_ADD_7(x) _REPEAT_ADD_6(_REPEAT_ADD_ONE(x)) 
#define _REPEAT_ADD_8(x) _REPEAT_ADD_7(_REPEAT_ADD_ONE(x)) 
#define _REPEAT_ADD_9(x) _REPEAT_ADD_8(_REPEAT_ADD_ONE(x)) 
#define _REPEAT_ADD_10(x) _REPEAT_ADD_9(_REPEAT_ADD_ONE(x)) 

sample.c:

#include "repeat.h" 

#define INNER_MACRO(inner, outer) if (inner == outer) printf("Match\n") 
#define INNER_BLOCK { if (inner == outer) printf("Match\n"); } 

#define OUTER_MACRO_INNER_MACRO(outer) REPEAT_ADD_ONE(INNER_MACRO, 3, 0, outer) 
#define OUTER_BLOCK_INNER_MACRO { REPEAT_ADD_ONE(INNER_MACRO, 3, 0, outer); } 
#define OUTER_MACRO_INNER_BLOCK(outer) REPEAT_ADD_ONE(INNER_BLOCK, 3, 0, outer) 
#define OUTER_BLOCK_INNER_BLOCK { REPEAT_ADD_ONE(INNER_BLOCK, 3, 0, outer); } 

void outer_macro_inner_macro() { 
    REPEAT_ADD_ONE(OUTER_MACRO_INNER_MACRO, 2, 1); 
} 

void outer_macro_inner_block() { 
    REPEAT_ADD_ONE(OUTER_MACRO_INNER_BLOCK, 2, 1); 
} 

void outer_block_inner_macro() { 
    REPEAT_ADD_ONE(OUTER_BLOCK_INNER_MACRO, 2, 1); 
} 

void outer_block_inner_block() { 
    REPEAT_ADD_ONE(OUTER_BLOCK_INNER_BLOCK, 2, 1); 
} 

W sample.c Mam pokazano cztery odmiany, które zbliżają się do tego, co chcę. Ale nikogo tam nie ma. Oto, co otrzymuję jako wyjście z "cpp sample.c> out.c; astyle out.c;"

void outer_macro_inner_macro() { 
    REPEAT_ADD_ONE(INNER_MACRO, 3, 0, 1); 
    REPEAT_ADD_ONE(INNER_MACRO, 3, 0, 2); 
} 

void outer_macro_inner_block() { 
    REPEAT_ADD_ONE({ if (inner == outer) printf("Match\n"); }, 3, 0, 1); 
    REPEAT_ADD_ONE({ if (inner == outer) printf("Match\n"); }, 3, 0, 2); 
} 

void outer_block_inner_macro() { 
    { 
     if (0 == outer) printf("Match\n"); 
     if (1 == outer) printf("Match\n"); 
     if (2 == outer) printf("Match\n"); 
    }(1); 
    { 
     if (0 == outer) printf("Match\n"); 
     if (1 == outer) printf("Match\n"); 
     if (2 == outer) printf("Match\n"); 
    }(2); 
} 

void outer_block_inner_block() { 
    { { 
      if (inner == outer) printf("Match\n"); 
     }(0, outer); 
     { 
      if (inner == outer) printf("Match\n"); 
     }(1, outer); 
     { 
      if (inner == outer) printf("Match\n"); 
     }(2, outer); 
    }(1); 
    { { 
      if (inner == outer) printf("Match\n"); 
     }(0, outer); 
     { 
      if (inner == outer) printf("Match\n"); 
     }(1, outer); 
     { 
      if (inner == outer) printf("Match\n"); 
     }(2, outer); 
    }(2); 
} 

A oto wyjście chcę dostać zamiast:

void desired_results() { 
    { 
     if (0 == 1) printf("Match\n"); 
     if (1 == 1) printf("Match\n"); 
     if (2 == 1) printf("Match\n"); 
    }; 
    { 
     if (0 == 2) printf("Match\n"); 
     if (1 == 2) printf("Match\n"); 
     if (2 == 2) printf("Match\n"); 
    }; 
} 

Zasadniczo mogę dostać rzeczy do pracy, jeśli mogę użyć bloku jako zewnętrznej ciała pętli, ale nie w przypadku korzystania z funkcji -jakie makro. Ale potrzebuję użyć makra z argumentami, aby ciała pętli mogły używać licznika pętli jako stałej zamiast zmiennej.

Problem z „makro” - „makro” sposób, że wewnętrzna rekurencyjne wywołanie REPEAT_ADD_ONE() nie jest rozszerzony. Odpowiedź wydaje się odraczać ekspansję wewnętrznej pętli, aż do momentu utworzenia zewnętrznej pętli, a następnie wymuszenie kolejnego przejścia, które rozszerza wewnętrzną pętlę. Ale z jakiegoś powodu moje podejście „random małpa” na które nie osiągnęło jeszcze rozwiązanie ...

[1] Understatement przeznaczeniem.

Odpowiedz

2

P99 może dostarczyć ci tego, czego szukasz. Posiada kilka rodzajów iteratorów makr, proste, takie jak: P99_UNROLL, P99_SER itp. I generyczny P99_FOR.

+0

Cześć Jens --- Wygląda ekscytująco, a ja na pewno to sprawdzę. Dokumentacja jest świetna! Wydaje się, że stosuje podejście oparte na NARGS i wykonuje całe przetwarzanie na argumentach variadic. Czy wiesz od ręki, czy pętla w pętli działa, i czy jest możliwe, aby makro pętli wewnętrznej odebrało licznik zewnętrznej pętli jako argument? –

+0

@NathanKurz, zagnieżdżanie dwóch pętli opartych na tym samym prymitywie nie działałoby łatwo, jak sądzę. Ale jeśli chodzi o przypadek użycia, wystarczy połączyć 'P99_FOR' z' P99_SER' (lub podobnym), które powinno być możliwe. Jedynym z tych konstruktów, które mają coś w rodzaju licznika pętli, jest 'P99_FoR', ale powinno być możliwe udostępnienie go innemu wewnętrznemu, iteratywnemu konstruktowi. –

1

Nie jestem pewien, czy śledzę wszystkie twoje makra. Ta odpowiedź here (również here teraz również) wyjaśnia, jak stworzyć ogólny cel REPEAT makro, tak:

#define REPEAT(count, macro, ...) \ 
    WHEN(count) \ 
    (\ 
     OBSTRUCT(REPEAT_INDIRECT)() \ 
     (\ 
      DEC(count), macro, __VA_ARGS__ \ 
     ) \ 
     OBSTRUCT(macro) \ 
     (\ 
      DEC(count), __VA_ARGS__ \ 
     ) \ 
    ) 
#define REPEAT_INDIRECT() REPEAT 

Dzieje zliczania, makra oraz dane użytkownika. Ponieważ makro przekazane jest odroczone, makro REPEAT można wywołać ponownie bezpośrednio i rekurencyjnie. Więc tutaj jest OUTER i INNER powtarzać makra:

#define OUTER(i, j) { REPEAT(j, INNER, i) } 
#define INNER(j, i) if (j == INC(i)) printf("Match\n"); 

EVAL(REPEAT(2, OUTER, 3)) 

Wyjście to będzie to:

{ 
    if (0 == 1) printf("Match\n"); 
    if (1 == 1) printf("Match\n"); 
    if (2 == 1) printf("Match\n"); 
} 
{ 
    if (0 == 2) printf("Match\n"); 
    if (1 == 2) printf("Match\n"); 
    if (2 == 2) printf("Match\n"); 
} 

Mam nadzieję, że to ma sens.

+0

Cześć Paul --- Bardzo dziękuję za odpowiedź i dziękuję za działający przykład. Przeczytałem twoją stronę i inne odpowiedzi tutaj kilka razy. Chociaż rozumiem to wiersz po linii, nadal jestem zdezorientowany przez to, jak działa ekspansja i jak interakcja pośrednia wpływa na (podwójne) odroczenie. Ale między twoimi przykładami a kodem Jen's P99 mam nadzieję, że to wszystko wymyślę. Czy są inne przydatne zasoby? –

+0

@NathanKurz Indywidualizacja jest konieczna, aby uniknąć otrzymania niebieskiego tokena. Mimo, że odroczone makro nie jest oceniane, jest skanowane przez preprocesor i gdy widzi token w kontekście wyłączającym (powiedz ponownie "REPEAT"), będzie malować na niebiesko i token nie będzie już mógł się rozszerzać. Dlatego umieściliśmy makro 'REPEAT_INDIRECT', aby preprocesor nie widział tokena' REPEAT', dopóki nie zostanie zastosowany inny skan, który będzie miał inny kontekst wyłączający, mam nadzieję, bez "REPEAT" w nim. –

+0

Również "OBSTRUCT" (lub podwójne opóźnienie) oznacza, że ​​potrzebne są dwa skany, aby w pełni rozwinąć makro. Tak więc 'ROZSZERZ (OBSTRUCT (makro))()' jest równoważne 'ODNÓW (makro)()'. Jest to konieczne dla 'REPEAT' z powodu warunku. Makro "KIEDY" zastosuje do niego jedno skanowanie. Zasadniczo, powodując, że 'OBSTRUCT (REPEAT_INDIRECT)()' staje się 'DEFER (REPEAT_INDIRECT)()'. Teraz, gdybyśmy zamiast tego mieli 'DEFER (REPEAT_INDIRECT)()', po zastosowaniu skanowania stanie się 'REPEAT', co spowoduje, że' REPEAT' stanie się pomalowany na niebiesko. Czy to ma sens? –

4

Vesa Karvonen's "Order" library/language może zdecydowanie zrobić to za Ciebie. Implementuje on nieograniczoną rekursję i pętlę w preprocesorze C, a jako naprawdę fajny bonus ubiera go z ładną zwięzłą składnią "właściwego" języka programowania (aby wyjaśnić: to nie jest alternatywny preprocesor, po prostu robi to lot do wklejenia tokenu, aby zachować krótkie słowa kluczowe - nadal jest to czysty CPP).

Używa zupełnie innej techniki, konwertując metaprogramy do CPS, a następnie przekazując je do konstrukcji pętli pojedynczej, która ma potencjalnie tryliony kroków i wykonuje metaprogram w sposób ściśle liniowy. Pętle i funkcje rekursywne mogą być zatem zagnieżdżone tak głęboko, jak chcesz, ponieważ nie mają oddzielnych sterowników, które wymagają interakcji i malują się nawzajem na niebiesko.

Tak, naprawdę, ktoś zaimplementował pełną maszynę wirtualną i interpreter używając makr CPP. To zastraszające.

(EDIT:. Spróbować the archived version jeśli kod Rosetta przestał działać dla ciebie też)

+0

Dzięki za wskaźnik. Źródło wydaje się również dostępne wraz z biblioteką Chaosu Mensonidesa: http://chaos-pp.cvs.sourceforge.net/viewvc/chaos-pp/ –

3

Z pomocą tutaj odpowiedzi (i studiowania P99, Chaos, Order i Cloak) Myślę, że mam dość proste i kompaktowy proof-of-concept (1). Ponieważ chciałem tylko "powtórzyć" funkcjonalność, a nie pełnowartościowy interpreter, poszedłem z nieco innym podejściem niż te inne rozwiązania. Zamiast tworzyć ogólne makra "if", "while" lub "when", zamiast tego użyłem bezpośrednio serii makr "dekrementujących", które rozszerzają się do pożądanego makra plus wywołanie makra dla n-1.

#ifndef _REPEAT_H 
#define _REPEAT_H 

// Usage: REPEAT_ADD_ONE(macro, times, start_n, macro_args...) 
//  Recursion allowed if inner macros use REPEAT_ADD_ONE_INNER(). 
//  This demo header only allows 3 layers of recursion and max n=10. 
//  Sample code at bottom. 

#define REPEAT_ADD_ONE(macro, times, start_n, macro_args...)   \ 
    _REPEAT_EXPAND_3(REPEAT_ADD_ONE_INNER(macro, times, start_n, ## macro_args)) 

#define REPEAT_ADD_ONE_INNER(macro, times, start_n, macro_args...)  \ 
    _REPEAT_ ## times(macro, start_n, _REPEAT_ADD_ONE, ## macro_args) 

#define _REPEAT_0(args...) /* empty */ 
#define _REPEAT_1(macro, n, func, args...) _REPEAT_DEFER(macro)(n, ## args) 
#define _REPEAT_2(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_1(m, f(n), f, ## a) 
#define _REPEAT_3(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_2(m, f(n), f, ## a) 
#define _REPEAT_4(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_3(m, f(n), f, ## a) 
#define _REPEAT_5(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_4(m, f(n), f, ## a) 
#define _REPEAT_6(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_5(m, f(n), f, ## a) 
#define _REPEAT_7(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_6(m, f(n), f, ## a) 
#define _REPEAT_8(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_7(m, f(n), f, ## a) 
#define _REPEAT_9(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_8(m, f(n), f, ## a) 
#define _REPEAT_10(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_9(m, f(n), f, ## a) 
// ... 

#define _REPEAT_ADD_ONE(n, ignore...) _REPEAT_ADD_ONE_ ## n 
#define _REPEAT_ADD_ONE_0 1 
#define _REPEAT_ADD_ONE_1 2 
#define _REPEAT_ADD_ONE_2 3 
#define _REPEAT_ADD_ONE_3 4 
#define _REPEAT_ADD_ONE_4 5 
#define _REPEAT_ADD_ONE_5 6 
#define _REPEAT_ADD_ONE_6 7 
#define _REPEAT_ADD_ONE_7 8 
#define _REPEAT_ADD_ONE_8 9 
#define _REPEAT_ADD_ONE_9 10 
#define _REPEAT_ADD_ONE_10 11 
// ... 

#define _REPEAT_EMPTY() 
#define _REPEAT_DEFER(token) token _REPEAT_EMPTY() 

#define _REPEAT_EXPAND_3(args...) _REPEAT_EXPAND(_REPEAT_EXPAND(_REPEAT_EXPAND(args))) 
#define _REPEAT_EXPAND(args...) args 
// ... 

#endif // _REPEAT_H 

#ifdef SAMPLE_CODE 
// to generate code: cpp -DSAMPLE_CODE sample.c 
// or easier to read: cpp -DSAMPLE_CODE sample.c > out.c; astyle out.c; less out.c 
// to compile and run: gcc -Wall -O3 -DSAMPLE_CODE sample.c -o sample 

int printf(const char *format, ...); 

#define BODY(i) printf("%d\n", i); 
void simple(void) { 
    REPEAT_ADD_ONE(BODY, 5, 1); 
} 

#define INNER(k, j, i) \ 
    printf("(%d, %d, %d)\n", i, j, k);   \ 
    if (i == j && j == k) printf("Match!\n") 
#define MIDDLE(j, i) REPEAT_ADD_ONE_INNER(INNER, 2, 2, j, i) 
#define OUTER(i) REPEAT_ADD_ONE_INNER(MIDDLE, 3, 0, i) 
void recursive(void) { 
    REPEAT_ADD_ONE(OUTER, 2, 1); 
} 

int main() { 
    simple(); 
    recursive(); 
    return 0; 
} 

#endif // SAMPLE_CODE 

nadal walczę, aby zrozumieć wiele subtelności, ale jak inni wskazał, ogólna zasada jest taka, że ​​nie może się rozwijać makro. Sposób obejścia tego polega na utworzeniu makra, które rozszerza się do punktu, w którym sam się nazywa, a następnie umieszczeniu opakowania wokół tego wyniku w celu dokończenia ekspansji.

Podstawową sztuczką, którą wykorzystałem, jest skorzystanie z faktu, że makro typu funkcji rozszerza się tylko wtedy, gdy zaraz po nim następuje nawias. Można użyć makro "odroczenia", które umieszcza "pusty" token między wywołaną nazwą makra a jego nawiasami, a następnie "rozwija" to jako argument do innego makra.

Ponieważ ekspansja argumentów odbywa się w innym kontekście niż początkowe rozwinięcie, początkowe makro zostanie ponownie rozwinięte. W moim rozwiązaniu jeden poziom rozszerzenia jest konieczny dla każdego poziomu potencjalnej rekursji. Jeśli grasz z kodem, aby go zrozumieć, może być przydatne zmniejszenie liczby rozszerzeń, aby sprawdzić wyniki pośrednie.

Dzięki za pomoc!

(1) To prawda, że ​​standard dla "racjonalnie prostych" jest dość luźny po zastosowaniu do rekurencyjnych makr preprocesorów. Jest jednak dość kompaktowy.

Powiązane problemy