Da ich leichte Probleme hatte, die Hilfe in Bezug auf externe Werkzeuge richtig zu interpretieren,
habe ich mal schnell eine Art Grundgerüst gebastelt um einen "Preprocessor" für PB zu bauen.
Mein Plan war es ursprünglich, automatisch Includedateien einzufügen. Daher ist das Gerüst auch
mehr oder weniger darauf ausgelegt.
Mit Hilfe von Little John's LPP habe ich mir dann das ganze zusammengereimt. Damit man nicht wie ich,
am Anfang wie das berühmte Schwein vorm Uhrwerk steht und fragend blickt, weils nicht funktioniert,
ein HowTo
Lange Rede kurzer Sinn :
HowTo : Wie schreibe ich einen Präprozessor für die Purebasic - IDE
Als allererstes : Was ist ein Präprozessor (engl. Preprocessor) ?
Stark vereinfacht : Der Präprozessor soll einen Code so verändern, das der Compiler mit dem Code auch etwas anfangen kann.
Damit kann man eine ganze Menge Sachen anstellen. z.B. könnte man alle im Code auftauchende Msg( in MessageRequester(
umwandeln..., einen OOP Konverter bauen (Gibt es schon), damit man in OOP Manier schreiben kann und der Compiler es auch versteht.
Die Möglichkeiten sind nur durch die Fantasie des Programmierers begrenzt.
Also schauen wir mal wie man so ein Ding zusammenbaut. Hier zeige ich ein "Grundgerüst", das man eigentlich für jeden erdenklichen
Anwendungsfall nehmen kann.
Das ganze ist stark kommentiert
Code: Alles auswählen
;:
;: Grundgerüst IDE - Tool : Präprozessor
;:
;: Einstellungen in der IDE : Menu -> Werkzeuge -> Werkzeuge konfigurieren :
;:
;: Man muss zwei externe Werkzeuge konfigurieren, damit das ganze einen Sinn macht.
;: Zum einen, wenn man in der IDE F5 drückt oder Kompilieren/Starten angeklickt wird
;:
;: Kommandozeile : Pfad zur exe des Präprozessors z.B. : D:\IDETools\pp.exe
;: Argumente : "%FILE" "%TEMPFILE" "%COMPILEFILE"
;: Arbeitsverzeichnis : leer lassen
;: Name : Präprozessor F5 (oder wie immer es beliebt)
;: Ereignis zum Auslösen des Werkzeugs : Vor dem kompilieren/Starten
;:
;: Auf der rechten Seite muss ausgewählt werden :
;: Warten bis zum Beenden des Werkzeugs
;:
;: Zum anderen, wenn eine Executable erstellt werden soll.
;:
;: Wenn der Präprozessor auch beim "Executable erstellen" ausgeführt werden soll
;: Ereignis zum Auslösen des Werkzeugs : Vor dem Erstellen des Executable
;:
;: Kommandozeile : Pfad zur exe des Präprozessors z.B. : D:\IDETools\pp.exe
;: Argumente : "%FILE" "%COMPILEFILE"
;: Arbeitsverzeichnis : leer lassen
;: Name : Präprozessor EXE (oder wie immer es beliebt)
;: Ereignis zum Auslösen des Werkzeugs : Vor dem Erstellen des Executable
;:
;: Wenn nun F5 gedrückt wird (also der einfache Start des Sources in der IDE)
;: wird bei gespeichertem Source der Filename in den Platzhalter %FILE geschrieben.
;: daraufhin können wir mit ProgramParameter(1) diesen auslesen. (Weil auch Leerzeichen in Pfaden oder
;: Filenamen existieren dürfen, werden die Platzhalter in " gesetzt.)
;:
;: Sollte das File noch nicht gespeichert worden sein, ist der Platzhalter %FILE leer (also = "")
;: dafür ist aber im Platzhalter %TEMPFILE der Pfad und Filename zu dem Source, der von der IDE vorher
;: gespeichert wird.
;:
;: Nun haben wir den originalen Sourcecode-Filenamen mit Pfad. In dem Platzhalter %COMPILEFILE ist nun der
;: Filename, der letztendlich tatsächlich zum Compiler geschickt wird, damit dieser seine Arbeit macht.
;:
;: Also müssen wir jetzt nur noch den originalen "SourceCode" nach unserem Willen verändern, und in als
;: %COMPILEFILE abspeichern.
;:
;: Das gleiche Prinzip beim erstellen eines Executables. Nur das es da kein %TEMPFILE gibt, da die IDE nur einen
;: gespeicherten Code zum erstellen zulässt. (Daher fehlt auch ein Argument)
;:
;: Man sollte beim Einlesen des SourceCodes und beim Schreiben des Outputs, darauf achten, das das Stringformat
;: ausgelesen und wieder geschrieben wird.
;:
Global SourceFile.s ; Originaler SourceCode, der manipuliert werden soll
Global OutPutFile.s ; Der Filename des Codes, der zum Compiler geschickt wird
Global SourceFormat ; Das Stringformat des Sourcecodes.
;: Grundlagenprozedur : Start Parameter einlesen
;: : Dieses Grundgerüst benötigt 2 oder 3 Parameter.
;: : Diese müssen eingelesen werden. Am Ende brauchen wir zwei Filenamen inkl. Pfad.
;: : Hierbei kommen globale Variablen zum Einsatz, damit sie in allen Prozeduren genutzt
;: : werden können.
Procedure.i GetStartParameter()
Protected ParameterIndex = 0 ; Hier der Index unserer Argumentenzeile (beginnt mit 0)
SourceFile = ProgramParameter(ParameterIndex) ; Im ersten Parameter sollte der Filename eines gespeicherten Codes sein.
ParameterIndex + 1 ; Hier setzen wir jetzt den Index ein Feld weiter
If SourceFile = ""
; Wenn aber nicht, dann wurde der Code noch nicht offiziell gespeichert. Also ist es ein F5/Kompileren/Starten
; Ereignis, was ausgelöst wurde. Daher muss im 2. Parameter jetzt der FileName stehen.
SourceFile = ProgramParameter(ParameterIndex) ; Im ersten Parameter sollte der Filename eines gespeicherten Codes sein.
ParameterIndex + 1 ; Hier setzen wir jetzt den Index ein Feld weiter wir brauchen ja noch das OutputFile....
EndIf
;: Anhand unserer Variable ParameterIndex, sind wir nun an der richtigen Stelle, um das Outputfile zu bekommen.
;: Dieser kann nun 2 sein (Weil ein Executable erstellt werden soll) oder 3 (Weil z.B. F5 gedrückt wurde)
OutPutFile = ProgramParameter(ParameterIndex)
;: Und nun abschliessend noch eine Kontrolle ob alles vorhanden ist (Prüfung ob einer der beiden Strings leer ist)
If SourceFile = "" Or OutPutFile = ""
;: Meldung an den User
MessageRequester("Fehler", "Source/Output Datei konnte nicht gelesen werden")
;: Und da ohne beide ein weitermachen keinen Sinn hat.
End
EndIf
EndProcedure
;: Grundlagenprozedur : Originalen SourceCode einlesen.
;: : Am besten benutzt man eine Linklist um den Code einzulesen
;: : Hier habe ich als Rückgabewert die Anzahl der eingelesenen
;: : Zeilen des Sources gewählt. Das SourceFormat sollte als
;: : globale Variable gespeichert werden, da man es für das
;: : Output schreiben noch braucht
;: : Der FileName ist die globale Variable : SourceFile.s
Procedure.i ReadOriginalSource(List Source.s())
Protected ID
ID = ReadFile(#PB_Any, SourceFile) ; Das Sourcefile zum Lesen öffnen
If ID ; Wenn das öffnen erfolgreich war
SourceFormat = ReadStringFormat(ID) ; Das originale Stringformat ermitteln
While Not Eof(ID) ; Solange das Ende des Files nicht erreicht wurde, das ganze nochmal
AddElement(Source()) ; Der Liste Source ein Element hinzufügen
Source() = ReadString(ID, SourceFormat) ; Jetzt eine Zeile im richtigen Format einlesen
Wend
CloseFile(ID) ; Und das File wieder schliessen
Else ; wenn das öffnen nicht geklappt hat
;: Meldung an den Nutzer
MessageRequester("Fehler", "Source konnte nicht gelesen werden!")
;: Und ohne Source, macht es keinen Sinn weiterzumachen, also Programmende
End
EndIf
ProcedureReturn ListSize(Source())
EndProcedure
;: Grundlagenprozedur : OutPutCode schreiben.
;: : Auch hierfür habe ich eine Liste gewählt um die einzelnen Codezeilen
;: : zu speichern. Desweiteren braucht man dann hier das Stringformat aus dem originalen
;: : Sourcecode, damit das ganze auch von dem Compiler ordnungsgemäß eingelesen und
;: : verarbeitet werden kann.
;: : Der FileName ist die globale Variable : OutputFile.s
Procedure.i WriteOutputSource(List OutPut.s())
Protected ID
If ListSize(OutPut()) > 0 ; Kurze Kontrolle, ob überhaupt in der Liste etwas enthalten ist.
ID = CreateFile(#PB_Any, OutPutFile) ; File zum schreiben erstellen (oder überschreiben)
If ID ; Wenn das File also erstellt wurde
WriteStringFormat(ID, SourceFormat) ; Das Stringformat des originalen Sourcecodes schreiben
ForEach Output() ; Jetzt jedes Element der Liste aufrufen
WriteStringN(ID, OutPut(), SourceFormat) ; Und im originalen Stringformat in die Datei schreiben
Next
CloseFile(ID) ; Und das File schliessen
Else ; Und wenn das File nicht erstellt wurde....
;: Meldung an den Nutzer
MessageRequester("Fehler", "Output konnte nicht geschrieben werden!")
;: In den meisten Fällen wird das Programm hiernach nichts weiteres mehr machen
;: aber ich verzichte hier auf das END, für den Fall, dass man z.B. irgendwas löschen
;: möchte (TempFiles) ....
EndIf
EndIf
EndProcedure
;: Damit hätten wir schonmal die wichtigsten Prozeduren abgehakt.
;: Was man dann mit den Listen Source() und Output() anstellt, bleibt jedem selbst überlassen ;)
;: Hier jetzt noch das Programm selbst...
;: Definieren unserer beiden Listen
Define NewList Source.s() ; Die Liste des Originalen Sourcecodes
Define NewList OutPut.s() ; Und die Liste des Outputs definieren
GetStartParameter() ; Die Startparameter ermitteln
ReadOriginalSource(Source()) ; Den originalen Source einlesen
;: Nun möchte ich, das in jedem meiner Codes z.B. eine Konstante und eine Globale Variable deklariert wird
;: Das geschieht immer zeilenweise, so als würde man einen Code schreiben ;)
;: Element erstellen : Element mit Wert füllen
AddElement(Output()) : Output() = "#MeineKonstante = 123"
AddElement(Output()) : Output() = "" ; Eine Leere Zeile. Muss man nicht...
AddElement(Output()) : Output() = ~"Global MeinText.s = \"PräprozessorGrundgerüst\""
AddElement(Output()) : Output() = "" ; Eine Leere Zeile. Muss man nicht.
;: Man könnte auch gleich ganze IncludeFiles hier einbinden.
;: Diese werden genauso gelesen wie der Originale Source (nur mit einer anderen LinkList,
;: oder eingelesenen Zeilen einfach direkt an die Source() Liste anhängen)
;: An dieser Stelle hänge ich hier die Liste Source() an die Liste Output() dran
ForEach Source()
AddElement(Output())
Output() = Source()
Next
;: Nun schreiben wir unsere Manipulation zurück und dieses wird am Ende kompiliert
WriteOutputSource(OutPut())
;: Fertig
externen Werkzeugeinstellungen beglückt hat, kann man eigentlich schon loslegen und z.B. folgenden Code schreiben
(Es dient die Manipulation des obigen Grundgerüst als Vorlage)
Code: Alles auswählen
Debug #MeineKonstante
Debug MeinText
Code: Alles auswählen
123
PräprozessorGrundgerüst
Der Compiler bekommt jetzt nicht mehr nur den Code
Code: Alles auswählen
Debug #MeineKonstante
Debug MeinText
Code: Alles auswählen
#MeineKonstante = 123
Global MeinText.s = "PräprozessorGrundgerüst"
Debug #MeineKonstante
Debug MeinText
Aber selbst das kann man ändern.... nur mehr Aufwand, und vermutlich dann nicht mehr Crossplatform (Windows/MacOS/Linux)
Ich hoffe ich habe das ganze verständlich gemacht, und das Forum wird von Präprozessorcodes nur so überschwemmt