[Anfänger][HowTo] : Präprozessor als IDE Tool

Hier kannst du häufig gestellte Fragen/Antworten und Tutorials lesen und schreiben.
Benutzeravatar
Bisonte
Beiträge: 2427
Registriert: 01.04.2007 20:18

[Anfänger][HowTo] : Präprozessor als IDE Tool

Beitrag von Bisonte »

Hallo.

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
So wenn man dann alles fertig hat, sprich das Gerüst zu einem ausführbarem Programm gemacht hat, die IDE mit den
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
Nun werden bei Druck auf F5 in der Debug-Ausgabe

Code: Alles auswählen

123
PräprozessorGrundgerüst
erscheinen.

Der Compiler bekommt jetzt nicht mehr nur den Code

Code: Alles auswählen

Debug #MeineKonstante
Debug MeinText
sondern der Präprozessor macht daraus

Code: Alles auswählen

#MeineKonstante = 123

Global MeinText.s = "PräprozessorGrundgerüst"

Debug #MeineKonstante
Debug MeinText 
Es ersetzt mitnichten eine Userlibrary. Es funktioniert z.B. keine Autovervollständigung. Oder eine Anzeige in der Statusleiste der IDE.
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 :mrgreen:
PureBasic 6.04 LTS (Windows x86/x64) | Windows10 Pro x64 | Asus TUF X570 Gaming Plus | R9 5900X | 64GB RAM | GeForce RTX 3080 TI iChill X4 | HAF XF Evo | build by vannicom​​
Benutzeravatar
Josh
Beiträge: 1028
Registriert: 04.08.2009 17:24

Re: [Anfänger][HowTo] : Präprozessor als IDE Tool

Beitrag von Josh »

Leider mit den üblichen Nachteilen:

- Fehlermeldungen werden nicht auf die richtige Zeile zurückgeführt
- Debuggen nicht möglich

Für das Startfile kann man das noch hinbiegen, aber spätestens wenn Includefiles im Spiel sind, ist es endgültig vorbei.
Benutzeravatar
mk-soft
Beiträge: 3695
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: [Anfänger][HowTo] : Präprozessor als IDE Tool

Beitrag von mk-soft »

Das habe ich früher bei meinen OOP-Precompiler so gelöst das nur Änderungen in einer Zeile durchgeführt werden und neue Include-Dateien erstellte und eingebunden wurde.

P.S. In der ersten Zeile ein 'IncludeFile "TopFile.xxx" : 'und in der letzen Zeile ein 'IncludeFile "BottomFile.xxx"' eingefügt.
Somit verschiebt sich nichts.

Code: Alles auswählen

 ; Include TopFile
  FirstElement(Lines())
  Lines() = "Includefile " + #DQUOTE$ + topfile + #DQUOTE$ + " : " + Lines()
  
  ; letzte Zeile suchen
  LastElement(Lines())
  While Left(Lines(), 1) = ";"
    PreviousElement(Lines())
  Wend
  
  ; Include BottomFile
  AddElement(Lines())
  Lines() = "Includefile " + #DQUOTE$ + bottomfile + #DQUOTE$
In den automatisch erstellen Top und Bottom Files weitere includes, Macros, Proceduren, etc angelegt
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
Bisonte
Beiträge: 2427
Registriert: 01.04.2007 20:18

Re: [Anfänger][HowTo] : Präprozessor als IDE Tool

Beitrag von Bisonte »

Josh hat geschrieben:Leider mit den üblichen Nachteilen:

- Fehlermeldungen werden nicht auf die richtige Zeile zurückgeführt
- Debuggen nicht möglich

Für das Startfile kann man das noch hinbiegen, aber spätestens wenn Includefiles im Spiel sind, ist es endgültig vorbei.
Deswegen schieb ich auch :
Es ersetzt mitnichten eine Userlibrary
Aber mit der Technik, wie mk-soft es beschreibt (alles in eine Zeile), kann es zumindest nur um eine Zeile verrutscht noch hinhauen ohne das es kompliziert wird. Das Beispiel mit Automatisch Includefiles einbinden, war halt für mich selbst...

Aber im Grunde ist es ja jedem überlassen was damit gemacht wird, es geht ja nur um das WIE.... nicht um das WAS ;)
PureBasic 6.04 LTS (Windows x86/x64) | Windows10 Pro x64 | Asus TUF X570 Gaming Plus | R9 5900X | 64GB RAM | GeForce RTX 3080 TI iChill X4 | HAF XF Evo | build by vannicom​​
Benutzeravatar
mk-soft
Beiträge: 3695
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: [Anfänger][HowTo] : Präprozessor als IDE Tool

Beitrag von mk-soft »

Aber mit der Technik, wie mk-soft es beschreibt (alles in eine Zeile), kann es zumindest nur um eine Zeile verrutscht noch hinhauen ohne das es kompliziert wird. Das Beispiel mit Automatisch Includefiles einbinden, war halt für mich selbst...
Bei mir verrutscht keine Zeile :wink:

Der PreCompiler war echt klein. Keine 1000 Zeilen.
Wenn jemand daran Interesse hat, kann ich ja mal den PreCompiler mal Veröffentlichen. 8)
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
Bisonte
Beiträge: 2427
Registriert: 01.04.2007 20:18

Re: [Anfänger][HowTo] : Präprozessor als IDE Tool

Beitrag von Bisonte »

Interesse :allright:

Auch ein alter "Gaul" wie ich, kann manchmal noch was lernen :mrgreen:
PureBasic 6.04 LTS (Windows x86/x64) | Windows10 Pro x64 | Asus TUF X570 Gaming Plus | R9 5900X | 64GB RAM | GeForce RTX 3080 TI iChill X4 | HAF XF Evo | build by vannicom​​
Benutzeravatar
mk-soft
Beiträge: 3695
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: [Anfänger][HowTo] : Präprozessor als IDE Tool

Beitrag von mk-soft »

Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Antworten