Dies begann als SO-Frage, aber ich erkannte, dass es ziemlich unkonventionell ist und basierend auf der tatsächlichen Beschreibung auf den Websites möglicherweise besser für Programmierer geeignet ist, da die Frage viel konzeptionelles Gewicht hat.
Ich habe Clang LibTooling gelernt und es ist ein sehr leistungsfähiges Tool, das in der Lage ist, das gesamte " Nitty Gritty " des Codes auf freundliche Weise, dh auf semantische Weise und auch nicht durch Vermutung, aufzudecken. Wenn clang Ihren Code kompilieren kann, ist clang sicher über die Semantik jedes einzelnen Zeichens in diesem Code.
Lassen Sie mich jetzt einen Moment zurücktreten.
Es gibt viele praktische Probleme, die auftreten, wenn man sich mit der Metaprogrammierung von C ++ - Vorlagen beschäftigt (und insbesondere, wenn man sich über Vorlagen hinaus in das Gebiet cleverer, wenn auch erschreckender Makros wagt). Um ehrlich zu sein, sind viele der üblichen Verwendungszwecke von Vorlagen für viele Programmierer, auch für mich, etwas erschreckend.
Ich denke, ein gutes Beispiel wären Zeichenfolgen zur Kompilierungszeit . Dies ist eine Frage, die mittlerweile über ein Jahr alt ist, aber es ist klar, dass C ++ ab sofort dies für bloße Sterbliche nicht einfach macht. Das Betrachten dieser Optionen reicht zwar nicht aus, um Übelkeit für mich auszulösen, aber ich bin mir nicht sicher, ob ich magischen, maximal effizienten Maschinencode erstellen kann, der zu jeder ausgefallenen Anwendung passt, die ich für meine Software habe.
Ich meine, seien wir ehrlich, Leute, Saiten sind ziemlich einfach und grundlegend. Einige von uns möchten nur einen bequemen Weg, um Maschinencode auszugeben, bei dem bestimmte Zeichenfolgen wesentlich stärker "eingebrannt" sind, als dies beim einfachen Codieren der Fall ist. In unserem C ++ - Code.
Geben Sie clang und LibTooling ein, wodurch der abstrakte Syntaxbaum (AST) des Quellcodes verfügbar gemacht wird und eine einfache benutzerdefinierte C ++ - Anwendung die korrekte und zuverlässige Bearbeitung des Rohquellcodes (mithilfe Rewriter
) zusammen mit einem umfangreichen semantischen objektorientierten Modell von allem im AST ermöglicht. Es behandelt viele Dinge. Es kennt die Makroerweiterungen und lässt Sie diesen Ketten folgen. Ja, ich spreche von Transformation oder Übersetzung von Quellcode zu Quellcode.
Meine grundlegende These hier ist, dass Clang es uns jetzt ermöglicht, ausführbare Dateien zu erstellen, die selbst als ideale benutzerdefinierte Präprozessorstufen für unsere C ++ - Software fungieren können, und wir können diese Metaprogrammierstufen mit C ++ implementieren. Wir sind lediglich durch die Tatsache eingeschränkt, dass diese Phase Eingaben vornehmen muss, die gültigen C ++ - Code sind, und als Ausgabe mehr gültigen C ++ - Code erzeugen muss. Plus alle anderen Einschränkungen, die Ihr Build-System anwendet.
Die Eingabe muss zumindest sehr nahe am gültigen C ++ - Code liegen, denn schließlich ist clang das Compiler-Frontend, und wir stöbern nur herum und sind kreativ mit seiner API. Ich weiß nicht, ob es eine Möglichkeit gibt, eine neue zu verwendende Syntax zu definieren, aber wir müssen natürlich die Methoden entwickeln, um sie richtig zu analysieren und dem Clang-Projekt hinzuzufügen, um dies zu tun. Mehr zu erwarten bedeutet, etwas im Clang-Projekt zu haben, das außerhalb des Rahmens liegt.
Kein Problem. Ich würde mir vorstellen, dass einige No-Op-Makrofunktionen diese Aufgabe übernehmen können.
Eine andere Möglichkeit, das zu betrachten, was ich beschreibe, besteht darin, Metaprogrammierkonstrukte mit Laufzeit-C ++ zu implementieren, indem der AST unseres Quellcodes (dank clang und seiner API) manipuliert wird, anstatt sie mit den eingeschränkteren Tools zu implementieren, die in der Sprache selbst verfügbar sind. Dies hat auch deutliche Vorteile für die Kompilierungsleistung (vorlagenintensive Header verlangsamen die Kompilierung proportional zu der Häufigkeit, mit der Sie sie verwenden. Viele kompilierte Inhalte werden dann sorgfältig abgeglichen und vom Linker weggeworfen).
Dies geht jedoch zu Lasten der Einführung von ein oder zwei zusätzlichen Schritten in den Erstellungsprozess und auch der Anforderung, eine (zugegebenermaßen) etwas ausführlichere Software (aber zumindest eine einfache Laufzeit-C ++) als Teil unseres Tools zu schreiben .
Das ist nicht das ganze Bild. Ich bin mir ziemlich sicher, dass durch das Generieren von Code, der mit Kernsprachenfunktionen äußerst schwierig oder unmöglich ist, ein viel größerer Funktionsbereich zur Verfügung steht. In C ++ können Sie eine Vorlage oder ein Makro oder eine verrückte Kombination aus beiden schreiben, aber in einem Clang-Tool können Sie Klassen und Funktionen auf JEDE Weise ändern, die Sie mit C ++ zur Laufzeit erreichen können , während Sie vollen Zugriff auf den semantischen Inhalt haben. Neben Vorlagen und Makros und allem anderen.
Ich frage mich also, warum das nicht schon alle machen. Ist es so, dass diese Funktionalität von clang so neu ist und niemand mit der riesigen Klassenhierarchie von clangs AST vertraut ist? Das kann es nicht sein.
Vielleicht unterschätze ich die Schwierigkeit nur ein wenig, aber die "Manipulation von Zeichenfolgen zur Kompilierungszeit" mit einem Clang-Tool ist fast kriminell einfach. Es ist ausführlich, aber wahnsinnig einfach. Alles, was benötigt wird, sind eine Reihe von No-Op-Makrofunktionen, die den tatsächlichen realen std::string
Operationen zugeordnet sind. Das Clang-Plugin implementiert dies, indem es alle relevanten No-Op-Makroaufrufe abruft und die Operationen mit Zeichenfolgen ausführt. Dieses Tool wird dann als Teil des Erstellungsprozesses eingefügt. Während der Erstellung werden diese No-Op-Makrofunktionsaufrufe automatisch in ihren Ergebnissen ausgewertet und dann als einfache alte Zeichenfolgen zur Kompilierungszeit wieder in das Programm eingefügt. Das Programm kann dann wie gewohnt kompiliert werden. Tatsächlich ist dieses resultierende Programm daher auch viel portabler, da kein ausgefallener neuer Compiler erforderlich ist, der C ++ 11 unterstützt.
quelle
Antworten:
Ja, Virginia, da ist ein Weihnachtsmann.
Die Idee, Programme zum Ändern von Programmen zu verwenden, gibt es schon lange. Die ursprüngliche Idee kam von John von Neumann in Form von Computern mit gespeicherten Programmen. Maschinencode Das Ändern von Maschinencode auf willkürliche Weise ist jedoch ziemlich unpraktisch.
Die Leute wollen im Allgemeinen den Quellcode ändern . Dies wird meist in Form von Programmtransformationssystemen (PTS) realisiert .
PTS bietet im Allgemeinen für mindestens eine Programmiersprache die Möglichkeit, ASTs zu analysieren, diesen AST zu manipulieren und gültigen Quelltext neu zu generieren. Wenn Sie sich tatsächlich in den meisten gängigen Sprachen umsehen, hat jemand ein solches Tool (Clang ist ein Beispiel für C ++, der Java-Compiler bietet diese Funktion als API, Microsoft bietet Rosyln, Eclipse's JDT, ...) mit einer Prozedur an API, die eigentlich ziemlich nützlich ist. Für die breitere Community kann fast jede sprachspezifische Community auf so etwas verweisen, das mit unterschiedlichen Reifegraden implementiert wird (normalerweise bescheiden, viele "nur Parser, die ASTs produzieren"). Viel Spaß beim Metaprogrammieren.
[Es gibt eine reflexionsorientierte Community, die versucht, Metaprogrammierung innerhalb der Programmiersprache durchzuführen, jedoch nur eine Verhaltensänderung zur Laufzeit erreicht und nur in dem Maße, in dem die Sprachcompiler einige Informationen durch Reflektion verfügbar gemacht haben. Mit Ausnahme von LISP gibt es immer Details zu dem Programm, die nicht durch Reflexion verfügbar sind ("Luke, du brauchst die Quelle"), die immer die Möglichkeiten der Reflexion einschränken.]
Die interessanteren PTS tun dies für beliebige Sprachen (Sie geben dem Tool eine Sprachbeschreibung als Konfigurationsparameter, einschließlich mindestens der BNF). Mit einem solchen PTS können Sie auch eine "Quelle-zu-Quelle" -Transformation durchführen, z. B. Muster direkt mithilfe der Oberflächensyntax der Zielsprache angeben . Mit solchen Mustern können Sie interessierende Fragmente codieren und / oder Codefragmente suchen und ersetzen. Dies ist weitaus praktischer als die Programmier-API, da Sie nicht alle mikroskopischen Details der ASTs kennen müssen, um den größten Teil Ihrer Arbeit zu erledigen. Stellen Sie sich dies als Meta-Metaprogrammierung vor: -}
Ein Nachteil: Wenn das PTS nicht verschiedene Arten nützlicher statischer Analysen bietet (Symboltabellen, Steuerungs- und Datenflussanalysen), ist es schwierig, auf diese Weise wirklich interessante Transformationen zu schreiben, da Sie für die meisten praktischen Aufgaben Typen überprüfen und den Informationsfluss überprüfen müssen. Leider ist diese Fähigkeit in der allgemeinen PTS tatsächlich selten. (Es ist immer nicht verfügbar mit dem immer vorgeschlagenen "Wenn ich nur einen Parser hätte ...". Weitere Informationen zu "Life After Parsing" finden Sie in meiner Biografie.)
Es gibt einen Satz, der besagt, dass Sie eine beliebige Transformation durchführen können, wenn Sie das Umschreiben von Zeichenfolgen (also das Umschreiben von Bäumen) durchführen können. und daher stützen sich einige PTS darauf, um zu behaupten, dass Sie alles mit nur den von ihnen angebotenen Baumumschreibungen metrogrammieren können. Während der Satz in dem Sinne zufriedenstellend ist, dass Sie jetzt sicher sind, dass Sie alles tun können, ist er ebenso unbefriedigend wie die Fähigkeit einer Turingmaschine, etwas zu tun, die Programmierung einer Turingmaschine nicht zur Methode der Wahl macht. (Dasselbe gilt für Systeme mit nur prozeduralen APIs, wenn Sie damit willkürliche Änderungen am AST vornehmen können [und tatsächlich denke ich, dass dies nicht für Clang gilt]).
Was Sie wollen, ist das Beste aus beiden Welten, ein System, das Ihnen die Allgemeingültigkeit des sprachparametrisierten PTS-Typs (auch für die Verarbeitung mehrerer Sprachen) bietet, mit zusätzlichen statischen Analysen und der Möglichkeit, Transformationen von Quelle zu Quelle mit prozeduralen zu mischen APIs. Ich kenne nur zwei , die dies tun:
Wenn Sie die Sprachbeschreibungen und statischen Analysatoren nicht selbst schreiben möchten (für C ++ ist dies ein enormer Arbeitsaufwand, weshalb Clang sowohl als Compiler als auch als allgemeine prozedurale Metaprogrammiergrundlage konstruiert wurde), benötigen Sie ein PTS mit ausgereiften Sprachbeschreibungen schon verfügbar. Andernfalls verbringen Sie Ihre ganze Zeit damit, das PTS zu konfigurieren, und keiner erledigt die Arbeit, die Sie eigentlich tun wollten. [Wenn Sie eine zufällige Nicht-Mainstream-Sprache auswählen, ist dieser Schritt sehr schwer zu vermeiden].
Rascal versucht dies, indem er "OPP" (Other People's Parsers) kooptiert, aber das hilft nicht bei der statischen Analyse. Ich denke, sie haben Java ziemlich gut in der Hand, aber ich bin mir sehr sicher, dass sie kein C oder C ++ machen. Aber es ist ein akademisches Forschungswerkzeug; schwer zu beschuldigen.
Ich betone, dass für unser [kommerzielles] DMS-Tool Java, C, C ++ vollständige Frontends verfügbar sind. Für C ++ deckt es fast alles in C ++ 14 für GCC und sogar die Variationen von Microsoft (und wir polieren jetzt), die Makroerweiterung und das bedingte Management sowie die Steuerung und Datenflussanalyse auf Methodenebene ab. Und ja, Sie können Grammatikänderungen auf praktische Weise festlegen. Wir haben ein benutzerdefiniertes VectorC ++ - System für einen Client erstellt, das C ++ radikal erweitert hat, um die Anzahl der datenparallelen F90 / APL-Array-Operationen zu verwenden. DMS wurde verwendet, um andere massive Metaprogrammierungsaufgaben auf großen C ++ - Systemen auszuführen (z. B. Umgestaltung der Anwendungsarchitektur). (Ich bin der Architekt hinter DMS).
Viel Spaß beim Meta-Metaprogrammieren.
quelle
Die Metaprogrammierung in C ++ mit der Compiler-API (anstelle von Vorlagen) ist in der Tat interessant und praktisch möglich. Da die Metaprogrammierung (noch) nicht standardisiert ist, sind Sie an einen bestimmten Compiler gebunden, was bei Vorlagen nicht der Fall ist.
Viele Leute tun dies, aber in anderen Sprachen. Meiner Meinung nach sehen die meisten C ++ - (oder Java- oder C-) Entwickler die Notwendigkeit dafür (möglicherweise zu Recht) nicht oder sind nicht an Metaprogrammierungsansätze gewöhnt. Ich denke auch, dass sie mit den Refactoring- / Code-Generierungsfunktionen ihrer IDE zufrieden sind und dass alles, was schicker ist, als zu komplex / schwer zu warten / schwer zu debuggen angesehen werden kann. Ohne geeignete Werkzeuge könnte es wahr sein. Sie sollten auch Trägheit und andere nichttechnische Probleme berücksichtigen, z. B. die Einstellung und / oder Schulung von Mitarbeitern.
Übrigens, da wir Common Lisp und sein Makrosystem erwähnen (siehe Basiles Antwort), muss ich sagen, dass Clasp erst gestern veröffentlicht wurde (ich bin nicht angeschlossen):
Clasp soll eine konforme Common Lisp-Implementierung sein, die mit LLVM IR kompiliert wird. Darüber hinaus werden Clang-Bibliotheken (AST, Matcher) für den Entwickler verfügbar gemacht.
Erstens bedeutet dies, dass Sie in CL schreiben und C ++ nicht mehr verwenden können, außer wenn Sie die Bibliotheken verwenden (und wenn Sie Makros benötigen, verwenden Sie CL-Makros).
Zweitens können Sie in CL Tools für Ihren vorhandenen C ++ - Code schreiben (Analyse, Refactoring, ...).
quelle
Einige C ++ - Compiler verfügen über eine mehr oder weniger dokumentierte und stabile API, insbesondere die meisten Compiler für freie Software.
Clang / LLVM besteht hauptsächlich aus einer großen Anzahl von Bibliotheken, die Sie verwenden können.
Aktuelle GCC akzeptiert Plugins . Insbesondere können Sie es mit MELT erweitern (das selbst ein Meta-Plugin ist und Ihnen eine übergeordnete domänenspezifische Sprache zur Erweiterung von GCC zur Verfügung stellt).
Beachten Sie, dass die Syntax von C ++ in GCC (und wahrscheinlich auch nicht in Clang) nicht einfach erweiterbar ist. Sie können jedoch eigene Pragmas, integrierte Funktionen, Attribute und Compiler-Durchläufe hinzufügen, um das zu tun, was Sie möchten (möglicherweise auch einige Präprozessor-Makros bereitstellen, die diese Dinge aufrufen eine benutzerfreundliche Syntax zu geben).
Sie könnten an mehrstufigen Sprachen und Compilern interessiert sein, siehe z. B. das Implementieren mehrstufiger Sprachen mit ASTs, Gensym und Reflection von C.Calcagno et al. und arbeite um MetaOcaml herum . Sie sollten sich unbedingt die Makrofunktionen von Common Lisp ansehen . Und Sie können durch interessieren JIT - Bibliotheken wie libjit , GNU Blitz , auch LLVM oder einfach -bei Laufzeit - einig C ++ Code generieren, Gabel eine Zusammenstellung von in eine gemeinsam genutztes Objekt dynamischen Bibliothek, dann dlopen (3) , dass die gemeinsamen Objekt. Der Blog von J.Pitrat bezieht sich auch auf solche reflektierenden Ansätze. Und auch RefPerSys .
quelle