[ANTLR4] Probleme mit C Makros



  • Hallo zusammen,

    ich bin gerade dabei ein kleines Tool zu werkeln. Dazu muss ich diverse *.c und *.h parsen. Als Parsergenerator will ich Antlr4 einsetzen, da ich gute Erfahrungen damit gemacht und bisher alles hinbekommen habe. Bisher...

    Als Grundlage habe ich die C11-Grammatik von Terence Parr himself (Entwickler von Antlr) angezogen, in der Hoffnung, dass ich exakt nichts an der Grammatik anpassen muss. Musste ich aber...

    Und zwar gab es schlicht keine Lexer-Regel für #define und Konsorten. Gut, dachte ich, schreibe ich die halt... krieg ich aber nicht hin...

    Die Regel für #pragma auf #define anzupassen war der logische erste Versuch.

    PragmaDirective
    : '#' Whitespace? 'pragma' Whitespace ~[\r\n]*
    -> skip
    ;
    

    ->

    DefineDirective
    : '#' Whitespace? 'define' Whitespace ~[\r\n]*
    -> skip
    ;
    

    Das sieht auf den ersten Blick nicht schlecht aus, führt aber mit folgenden Testdaten bereits zu einem (dem größten zumindest) Problem:

    #define FOO /* wer auch immer solche
                   Blockkommentare verbrochen
                   hat gehoert entlassen */
    

    Ironischerweise demonstriert der Board-Highlighter das Problem direkt: Bin ich einmal in der Lexer-Regel DefineDirective drin terminiert diese bei einem '\n' bzw. '\r'. Danach versucht der Lexer irgendwas mit "Blockkommentare" anzufangen was ja nur noch schiefgehen kann...

    Ein erster kleiner Hack war dann, die Regel einfach auch bei einem erkannten '/' abzubrechen, so dass der dann folgende Blockkommentar (und genialerweise auch gleich ein //-Kommentar) auch mit der entsprechenden Regel BlockComment behandelt wird. Doch dieser Hack wird mir beim erstbesten Makro mit Division drin um die Ohren fliegen:

    #define BAR 2/1
    

    Ebenfalls nicht abgefangen wäre damit:

    #define BAZ 2 + /* wer sowas macht... */ 1
    

    Nun bin ich mit meinem Latein ein wenig am Ende und erhoffe mir eine Eingebung des Forums. Zwar bin ich mir recht sicher, dass ich das mit Atombomben-Hacks rund um Lexer-Modes und so Schweinkram hinfrickeln könnte, aber eine saubere Lösung wäre mir deutlich lieber, und genau die will mir nicht kommen...

    PS: Ganz wichtig: Ich will die Makros nur sauber durchlexen und dann verwerfen, ich will die nicht detailliert parsen oder gar evaluieren!!!

    Gruß Tim



  • das sollte ganz ohne Parsergeneratoren einfach schaffbar sein:

    preprocessor := "#" { (letter) | (whitespace) | (digit) | (multilinecomment) } "\n"
    multilinecomment := "/*" { (letter) | (whitespace) | (digit } "*/"
    

    | bedeutet oder
    {} bedeutet x-male Wiederholung



  • Tim_the_true_one schrieb:

    #define FOO /* wer auch immer solche
                   Blockkommentare verbrochen
                   hat gehoert entlassen */
    

    Das ist doch generell nicht möglich, außer du machst \ ans Ende der Zeilen.



  • fsfsfsfs schrieb:

    das sollte ganz ohne Parsergeneratoren einfach schaffbar sein:

    preprocessor := "#" { (letter) | (whitespace) | (digit) | (multilinecomment) } "\n"
    multilinecomment := "/*" { (letter) | (whitespace) | (digit } "*/"
    

    | bedeutet oder
    {} bedeutet x-male Wiederholung

    Dass man das ohne Parsergenerator irgendwie kann ist aber nicht die Frage, sondern wie man es ganz konkret mit diesem Parsergenerator hinbekommt.

    oenone schrieb:

    Tim_the_true_one schrieb:

    #define FOO /* wer auch immer solche
                   Blockkommentare verbrochen
                   hat gehoert entlassen */
    

    Das ist doch generell nicht möglich, außer du machst \ ans Ende der Zeilen.

    Das ist ja ein Blockkommentar, keine Multiline-Makro, warum sollte man in einem Blockkommentar \ benötigen?
    Selbst wenn es wie im Beispiel nicht erlaubt wäre, was ich nicht glaube, habe ich hier Code der genau dies macht.



  • Tim_the_true_one schrieb:

    fsfsfsfs schrieb:

    das sollte ganz ohne Parsergeneratoren einfach schaffbar sein:

    preprocessor := "#" { (letter) | (whitespace) | (digit) | (multilinecomment) } "\n"
    multilinecomment := "/*" { (letter) | (whitespace) | (digit } "*/"
    

    | bedeutet oder
    {} bedeutet x-male Wiederholung

    Dass man das ohne Parsergenerator irgendwie kann ist aber nicht die Frage, sondern wie man es ganz konkret mit diesem Parsergenerator hinbekommt.

    ich kenne die genaue Syntax zur Ansteuerung deines Generators nicht.
    Aber kann man dem nicht genau oben genannte Regel irgendwie beibringen?
    Irgendwie scheint das ja ein regulärer Ausdruck zu sein den du dem Generator da fütterst.
    Dein String sollte so aussehen:

    1. #
    2. define | pragma
    3. beliebig oft ( Leerzeichen | Ziffer | Buchstabe | Rechenzeichen | (NICHT /*) )
    

    regulärer Ausdruck für Präprozessor:

    #define[\s]*((?!/\*)[\w\*+-/()\s])*
    

    regulärer Ausdruck für mehrzeiligen Kommentar:

    /\*.*?\*/
    

    Kurz mit Python angetestet:

    #!/usr/bin/python
    import re
    
    # Originalstring sowie regulaerer Ausdruck
    s='#define x/2*5 /*kommentar*/'
    regexkommentar='''/\*.*?\*/'''
    regexmakro='''#define[\s]*((?!/\*)[\w\*+-/()\s])*'''
    
    # Suche nach Kommentar
    m=re.search(regexkommentar,s)
    print m.group(0) # liefert: /*kommentar*/
    
    # Suche nach Praeprozessoranweisung
    m=re.search(regexmakro,s)
    print m.group(0) # liefert #define x/2*5
    


  • oenone schrieb:

    Tim_the_true_one schrieb:

    #define FOO /* wer auch immer solche
                   Blockkommentare verbrochen
                   hat gehoert entlassen */
    

    Das ist doch generell nicht möglich, außer du machst \ ans Ende der Zeilen.

    sowohl

    #define QUADR(x) (x)*(x)/*hallo1
    hallo2*/
    

    als auch

    #define QUADR(x) (x)/*hallo1*/*(x)
    

    ist ok.

    Fraglich ist natürlich, wie sinnvoll es ist, von all zu wilden Konstrukten Gebrauch zu machen. Aber das ist ein anderes Thema ...



  • Der C (und C++) Präprozessor arbeitet in vier Durchgängen: u.a. werden alle Kommentare entfernt (und mit einem Leerzeichen ersetzt) - und erst danach werden die #-Befehle (#include, #define,#ifdef etc.) abgearbeitet, s.a. C-Präprozessor - Phasen

    Das alles mit einer Grammatik zu erschlagen wird schwierig werden.



  • Th69 schrieb:

    Der C (und C++) Präprozessor arbeitet in vier Durchgängen: u.a. werden alle Kommentare entfernt (und mit einem Leerzeichen ersetzt) - und erst danach werden die #-Befehle (#include, #define,#ifdef etc.) abgearbeitet, s.a. C-Präprozessor - Phasen

    Das alles mit einer Grammatik zu erschlagen wird schwierig werden.

    Jein, ANTLR wird entweder von Netbeans C++ und Eclipse CDT verwendet oder sorgar von beiden. Es ist viel Arbeit, aber es ist nicht unmöglich.


Anmelden zum Antworten