Zaczęło się od pytania SO, ale zdałem sobie sprawę, że jest to dość niekonwencjonalne i oparte na rzeczywistym opisie na stronach internetowych, może być lepiej dostosowane do programistów.se, ponieważ pytanie ma wiele pojęć koncepcyjnych.
Uczyłem się języka LibTooling i jest to bardzo potężne narzędzie, które może w przyjazny sposób ujawnić cały „drobiazgowy” charakter kodu, to znaczy semantycznie , a nie zgadując. Jeśli clang może skompilować kod, to clang jest pewien semantyki każdego pojedynczego znaku w tym kodzie.
Pozwól mi teraz cofnąć się na chwilę.
Istnieje wiele praktycznych problemów, które pojawiają się, gdy ktoś angażuje się w metaprogramowanie szablonów C ++ (a zwłaszcza gdy wyrusza poza szablony na terytorium sprytnych, choć przerażających makr). Szczerze mówiąc, dla wielu programistów, w tym mnie, wiele zwykłych zastosowań szablonów jest również nieco przerażających.
Wydaje mi się, że dobrym przykładem byłyby łańcuchy czasu kompilacji . To pytanie ma już ponad rok, ale jasne jest, że C ++ w tej chwili nie ułatwia tego zwykłym śmiertelnikom. Chociaż patrzenie na te opcje nie wystarcza, aby wywołać u mnie mdłości, niemniej jednak nie jestem przekonany, czy jestem w stanie stworzyć magiczny, maksymalnie wydajny kod maszynowy, pasujący do każdej fantazyjnej aplikacji, którą mam dla mojego oprogramowania.
Spójrzmy prawdzie w oczy, ludzie, łańcuchy są dość proste i podstawowe. Niektórzy z nas chcą po prostu wygodnego sposobu na wyemitowanie kodu maszynowego, który ma pewne „ciągłe” ciągi znaków znacznie więcej niż dostajemy, gdy kodujemy go w prosty sposób. W naszym kodzie C ++.
Wpisz clang i LibTooling, który udostępnia abstrakcyjne drzewo składniowe (AST) kodu źródłowego i pozwala prostej niestandardowej aplikacji C ++ poprawnie i niezawodnie manipulować surowym kodem źródłowym (za pomocą Rewriter
) wraz z bogatym semantycznym obiektowym modelem zorientowanym na wszystko w AST. Obsługuje wiele rzeczy. Wie o rozszerzeniach makr i pozwala śledzić te łańcuchy. Tak, mówię o transformacji kodu źródłowego lub tłumaczeniu.
Moją podstawową tezą jest to, że clang pozwala nam teraz tworzyć pliki wykonywalne, które same mogą działać jako idealne niestandardowe etapy preprocesora dla naszego oprogramowania C ++, a my możemy zaimplementować te etapy metaprogramowania w C ++. Jesteśmy po prostu ograniczeni przez fakt, że ten etap musi pobierać dane wejściowe, które są poprawnym kodem C ++ i produkować jako wynik bardziej poprawny kod C ++. Plus wszelkie inne ograniczenia, które stosuje system kompilacji.
Dane wejściowe muszą być co najmniej bardzo zbliżone do poprawnego kodu C ++, ponieważ w końcu clang jest frontonem kompilatora, a my po prostu szukamy i jesteśmy kreatywni dzięki jego interfejsowi API. Nie wiem, czy istnieje jakikolwiek przepis umożliwiający zdefiniowanie nowej składni do użycia, ale najwyraźniej musimy opracować sposoby jej prawidłowej analizy i dodać ją do projektu clang, aby to zrobić. Oczekiwać więcej, to mieć coś w projekcie clang, który jest poza zakresem.
Żaden problem. Wyobrażam sobie, że niektóre funkcje makr bez obsługi poradzą sobie z tym zadaniem.
Innym sposobem spojrzenia na to, co opisuję, jest zaimplementowanie konstrukcji metaprogramowania przy użyciu środowiska wykonawczego C ++ poprzez manipulowanie AST naszego kodu źródłowego (dzięki clang i jego API) zamiast implementowania ich przy użyciu bardziej ograniczonych narzędzi dostępnych w samym języku. Ma to również wyraźne zalety w zakresie wydajności kompilacji (nagłówki z dużym szablonem spowalniają kompilację proporcjonalnie do tego, jak często ich używasz. Wiele skompilowanych rzeczy jest następnie starannie dopasowywanych i wyrzucanych przez linker).
Jest to jednak kosztem wprowadzenia dodatkowego kroku lub dwóch w procesie kompilacji, a także wymogu napisania nieco (co prawda) nieco bardziej szczegółowego oprogramowania (ale przynajmniej jest to proste środowisko uruchomieniowe C ++) jako część naszego narzędzia .
To nie jest cały obraz. Jestem pewien, że generowanie kodu jest o wiele większe niż w przypadku podstawowych funkcji językowych. W C ++ możesz napisać szablon lub makro lub szaloną kombinację obu, ale w narzędziu clang możesz modyfikować klasy i funkcje w KAŻDY sposób, który możesz osiągnąć w C ++, w czasie wykonywania , mając jednocześnie pełny dostęp do treści semantycznej, oprócz szablonu i makr oraz wszystkiego innego.
Zastanawiam się więc, dlaczego wszyscy już tego nie robią. Czy to dlatego, że ta funkcja clang jest tak nowa i nikt nie zna ogromnej hierarchii klas AST klanu? To nie może być to.
Być może po prostu trochę nie doceniam trudności, ale „manipulowanie ciągiem znaków w czasie kompilacji” za pomocą narzędzia clang jest prawie kryminalnie proste. Jest pełny, ale niesamowicie prosty. Wszystko, czego potrzeba, to kilka makropoleceń, które odwzorowują rzeczywiste std::string
operacje. Wtyczka clang implementuje to, pobierając wszystkie odpowiednie makropolecenia no-op i wykonuje operacje na łańcuchach. To narzędzie jest następnie wstawiane jako część procesu kompilacji. Podczas kompilacji te nie wywoływane funkcje makr są automatycznie analizowane w wynikach, a następnie wstawiane z powrotem jako zwykłe stare ciągi czasu kompilacji w programie. Program można następnie skompilować jak zwykle. W rzeczywistości ten wynikowy program jest również znacznie bardziej przenośny, nie wymagając wymyślnego nowego kompilatora obsługującego C ++ 11.
źródło
Odpowiedzi:
Tak, Virginia, jest Święty Mikołaj.
Pojęcie używania programów do modyfikowania programów istnieje już od dawna. Pierwotny pomysł przyszedł od Johna von Neumanna w postaci komputerów z programami przechowywanymi. Ale modyfikowanie kodu maszynowego w dowolny sposób jest dość niewygodne.
Ludzie zazwyczaj chcą modyfikować kod źródłowy . Jest to głównie realizowane w postaci systemów transformacji programu (PTS) .
Ogólnie rzecz biorąc, PTS oferuje, dla co najmniej jednego języka programowania, możliwość analizowania AST, manipulowania nim i regenerowania poprawnego tekstu źródłowego. Jeśli faktycznie przekopujesz się, w większości popularnych języków ktoś zbudował takie narzędzie (Clang jest przykładem dla C ++, kompilator Java oferuje tę funkcję jako API, Microsoft oferuje Rosyln, JDT Eclipse, ...) z procedurami Interfejs API, który jest całkiem przydatny. W szerszej społeczności prawie każda społeczność specyficzna dla języka może wskazywać na coś takiego, wdrażanego z różnym poziomem dojrzałości (zwykle skromnym, wielu „tylko parserów produkujących AST”). Szczęśliwego metaprogramowania.
[Jest odbiciem zorientowane na społeczność, która próbuje zrobić metaprogramowanie od wewnątrz języka programowania, ale tylko osiągnąć „wykonania” zachowanie modifiation, i tylko w takim zakresie, że kompilatory język jakąś dostępną przez odbicie informacje. Z wyjątkiem LISP, zawsze są szczegóły dotyczące programu, które nie są dostępne przez odbicie („Luke, potrzebujesz źródła”), które zawsze ograniczają możliwości odbicia.]
Bardziej interesujące PTS robią to dla dowolnych języków (podajesz narzędziu opis języka jako parametr konfiguracyjny, w tym przynajmniej BNF). Taki PTS pozwala także na transformację „źródło do źródła”, np. Określenie wzorów bezpośrednio przy użyciu składni powierzchniowej języka docelowego; za pomocą takich wzorców możesz kodować interesujące fragmenty i / lub znajdować i zamieniać fragmenty kodu. Jest to o wiele wygodniejsze niż programowanie API, ponieważ nie musisz znać wszystkich mikroskopijnych szczegółów dotyczących AST, aby wykonać większość swojej pracy. Potraktuj to jako meta-metaprogramowanie: -}
Wada: jeśli PTS nie oferuje różnego rodzaju przydatnych analiz statycznych (tabele symboli, analizy i analizy przepływu danych), trudno jest w ten sposób napisać naprawdę interesujące transformacje, ponieważ trzeba sprawdzać typy i weryfikować przepływ informacji dla większości praktycznych zadań. Niestety, ta funkcja jest w rzeczywistości rzadka w ogólnym PTS. (Jest to zawsze niedostępne w przypadku zawsze proponowanego „Gdybym tylko miał parser ...” Zobacz moją biografię, aby uzyskać dłuższą dyskusję na temat „Life After Parsing”).
Istnieje twierdzenie, które mówi, że jeśli możesz przerabiać napisy [a więc przepisywać drzewo], możesz wykonać dowolną transformację; a więc wielu PTS opiera się na tym, aby twierdzić, że można metaprogramować wszystko za pomocą tylko przepisanych drzewek. Chociaż twierdzenie to jest satysfakcjonujące w tym sensie, że jesteś pewien, że możesz zrobić wszystko, nie jest satysfakcjonujące w ten sam sposób, w jaki zdolność maszyny Turinga do robienia czegokolwiek nie czyni programowania maszyny Turinga wybraną metodą. (To samo dotyczy systemów z tylko proceduralnymi interfejsami API, jeśli pozwolą one na wprowadzanie dowolnych zmian w AST [i faktycznie myślę, że nie jest tak w przypadku Clanga]).
To, czego chcesz, to najlepsze z obu światów, system, który oferuje ogólność sparametryzowanego języka PTS (nawet obsługując wiele języków), z dodatkowymi analizami statycznymi, możliwością łączenia transformacji między źródłami a procedurami Pszczoła. Znam tylko dwa , które to robią:
O ile nie chcesz samodzielnie pisać opisów języków i analizatorów statycznych (dla C ++ jest to ogromna ilość pracy, dlatego Clang został zbudowany zarówno jako kompilator, jak i jako podstawa metaprogramowania proceduralnego), będziesz potrzebować PTS z dojrzałymi opisami języków już dostępny. W przeciwnym razie poświęcisz cały swój czas na konfigurowanie PTS i nikt nie wykona pracy, którą naprawdę chciałeś wykonać. [Jeśli wybierzesz przypadkowy, niemainstreamowy język, tego kroku bardzo trudno uniknąć].
Rascal próbuje to zrobić, wybierając opcję „OPP” (parsery innych osób), ale to nie pomaga w części dotyczącej analizy statycznej. Wydaje mi się, że mają całkiem dobrą Javę, ale jestem pewien, że nie używają C ani C ++. Ale jest narzędziem akademickim; ciężko ich winić.
Podkreślam, że nasze [komercyjne] narzędzie DMS ma dostępne pełne interfejsy Java, C, C ++. W przypadku C ++ obejmuje prawie wszystko w C ++ 14 dla GCC, a nawet warianty Microsoftu (i teraz dopracowujemy), ekspansję makr i zarządzanie warunkowe oraz kontrolę na poziomie metody i analizę przepływu danych. I tak, można określić gramatyki zmiany w praktyczny sposób; zbudowaliśmy niestandardowy system VectorC ++ dla klienta, który radykalnie rozszerzył C ++, aby wykorzystać ilość operacji na równoległych tablicach danych F90 / APL. DMS został wykorzystany do wykonywania innych masowych zadań metaprogramowania na dużych systemach C ++ (np. Przekształcanie architektury aplikacji). (Jestem architektem DMS).
Szczęśliwego meta-metaprogramowania.
źródło
Metaprogramowanie w C ++ z API kompilatorów (zamiast szablonów) jest rzeczywiście interesujące i praktycznie możliwe. Ponieważ metaprogramowanie nie jest (jeszcze) znormalizowane, będziesz przywiązany do konkretnego kompilatora, co nie ma miejsca w przypadku szablonów.
Wiele osób to robi, ale w innych językach. Moim zdaniem większość programistów C ++ (lub Java lub C) nie widzi potrzeby (być może słusznie) lub nie jest przyzwyczajona do metod metaprogramowania; Myślę też, że są zadowoleni z funkcji refaktoryzacji / generowania kodu w swoim IDE i że każdy fanator może być postrzegany jako zbyt skomplikowany / trudny do utrzymania / trudny do debugowania. Bez odpowiednich narzędzi może to być prawda. Należy również wziąć pod uwagę bezwładność i inne problemy pozatechniczne, takie jak zatrudnianie i / lub szkolenie ludzi.
Nawiasem mówiąc, ponieważ wspominamy o Common Lisp i jego makrosystemie (patrz odpowiedź Basile'a), muszę powiedzieć, że zaledwie wczoraj Clasp zostało wydane (nie jestem związany):
Clasp ma być zgodną implementacją Common Lisp, która kompiluje się z LLVM IR. Ponadto udostępnia programistom biblioteki Clanga (AST, Matcher).
Po pierwsze, oznacza to, że możesz pisać w CL i nie używać C ++, z wyjątkiem korzystania z jego bibliotek (i jeśli potrzebujesz makr, użyj makr CL).
Po drugie, możesz pisać narzędzia w języku CL dla istniejącego kodu C ++ (analiza, refaktoryzacja, ...).
źródło
Kilka kompilatorów C ++ ma mniej lub bardziej udokumentowane i stabilne API, w szczególności większość kompilatorów wolnego oprogramowania.
Clang / LLVM to przeważnie duży zestaw bibliotek i można z nich korzystać.
Najnowsze GCC akceptuje wtyczki . W szczególności możesz go rozszerzyć za pomocą MELT (który sam w sobie jest meta-wtyczką, która udostępnia język specyficzny dla domeny wyższego poziomu w celu rozszerzenia GCC).
Zauważ, że składnia C ++ nie jest łatwo rozszerzalna w GCC (i prawdopodobnie nie w Clang), ale możesz dodać własne pragmy, wbudowane atrybuty i przepustki kompilatora, aby zrobić to, co chcesz (być może także udostępniając niektóre makra preprocesora wywołujące te rzeczy w celu uzyskania przyjaznej dla użytkownika składni).
Być może zainteresują Cię wieloetapowe języki i kompilatory, patrz np. Wdrażanie języków wieloetapowych za pomocą AST, Gensym i Refleksji autorstwa C.Calcagno i in. i obejdź MetaOcaml . Z pewnością powinieneś zajrzeć do obiektów makro Common Lisp . Biblioteki JIT, takie jak libjit , błyskawica GNU , a nawet LLVM , lub po prostu -w czasie wykonywania mogą Cię zainteresować - wygeneruj kod C ++, rozwidlaj jego kompilację w bibliotece dynamicznej obiektów współdzielonych, a następnie dlopen (3), który udostępnił obiekt. Blog J.Pitrat jest również powiązany z takimi refleksyjnymi podejściami. A także RefPerSys .
źródło