Sinnvolles goto - Skipping von "escaped characters"



  • // skip to next unescaped double quote -- end of string literal
    for(; it != end && *it != '"'; ++it)
        if (*it == '\\' && (it+1) != end)
            ++it;
    

    ist das ne angewohnheit von C++lern, dass jeder code sehr lang sein muss, viel "std::" enthalten muss und relativ kompliziert sein muss?

    schoener code und ein beschreibender kommentar reicht doch.

    sleepy: dein code ueberrennt auch normale quotes, wenn ich das char-literal richtig interpretiere: '\"' sollte ein '"' sein, iirc

    hustbaer: du kopierst zeichen herum. hat optimierungspotential.

    warum zum geier lassen sich iteratoren nur auf (un)gleichheit und nicht auf relation vergleichen? wuerde den code so viel sinnvoller machen.

    camper: iteratoren mit index-operator? wenns das gibt, dann kann ich doch auch auf relation testen, oder?


  • Mod

    c.rackwitz schrieb:

    warum zum geier lassen sich iteratoren nur auf (un)gleichheit und nicht auf relation vergleichen? wuerde den code so viel sinnvoller machen.

    camper: iteratoren mit index-operator? wenns das gibt, dann kann ich doch auch auf relation testen, oder?

    Korrekt. Beides ist für random-access-Iteratoren definiert. Damit erübrigt sich die Frage nach dem Test mit != an Stelle von <. Mit != ist es nicht auf diese Iteratorkategorie beschränkt. Zudem ist für mich nicht erkennbar, was ein < hier an zusätzlichem Sinn hineinbringen soll.



  • c.rackwitz schrieb:

    // skip to next unescaped double quote -- end of string literal
    for(; it != end && *it != '"'; ++it)
        if (*it == '\\' && (it+1) != end)
            ++it;
    

    man fummelt eigentlich nicht innendrin an der schleifenvariablen herum.
    bisher gefällt mir keine der lösungen. klingt danach, als sollte man den umgebenden code umschubsen.
    wenn das nicht in frage kommt, gewinnt c.rackwitz.



  • Any schrieb:

    Mein kleiner Parser soll strings welche innerhalb von "" stehen parsen. Damit man innerhalb von strings auch " verwenden kann, muss man es mit einem \ escapen.
    Damit der Parser diese escapten " überspringt hab ich das ganze so gelöst:

    else if( *it == '\"' ) //beginning of a string
    {
    	std::string::const_iterator begin( ++it );
    
    continueSkipping:
    	while( it != end && *it != '\"' )
    	{
    		++it;
    	}
    			
    	if( *( it - 1 ) == '\\' )//an escaped quotation character
    	{
    		++it;
    		goto continueSkipping;	//go back and continue skipping the strings content
    	}
    
            argVec.push_back( std::string( begin, it++ ) );
    
    	//entfernt die backslashes der escapten "
    	boost::algorithm::erase_all( argVec[ argVec.size() - 1 ], "\\" );
    }
    

    Nach reiflicher überlegung fiel mir kein direkterer Weg ein als ein goto zu benutzen.
    Alle alternativen die mir in den Kopf kommen sind deutlich komplizierter gestrickt.

    Hat jemand von euch eine einfacherere Lösung?

    Gruß Any

    Nach einen flüchtigen Blick auf den code kommt mir das in den Sinn:

    Als Pseudocode:

    • solange 2 Bedingungen erfüllt sind, wiederhole etwas.
    • wenn eine weitere Bedingung auch noch erfüllt ist, mache es auch und mache mit der Wiederholung weiter

    Vereinfacht:

    • solange 2 Bedinung und noch eine weitere erfüllt ist, wiederhole etwas.

    also:

    • solange 3 Bedingungen erfüllt sind, wiederhole etwas.

    Hier als code:

    else if( *it == '\"' ) //beginning of a string
    {
    	std::string::const_iterator begin( ++it );
    
    continueSkipping:
    	while( it != end && *it != '\"'
    	     &&  *( it - 1 ) == '\\' )//an escaped quotation character
    	{
    		++it;
    	}
    
            argVec.push_back( std::string( begin, it++ ) );
    
    	//entfernt die backslashes der escapten "
    	boost::algorithm::erase_all( argVec[ argVec.size() - 1 ], "\\" );
    }
    

    Wobei ich einfach einen stateful Parser schreiben würde.

    Tntnet



  • tntnet schrieb:

    Wobei ich einfach einen stateful Parser schreiben würde.

    !!!

    Danke.



  • hustbaer: du kopierst zeichen herum. hat optimierungspotential

    Leute, sagt mir bitte nicht dass mein Code langsam(er als ein anderer) ist, ohne es ausprobiert zu haben. Ich denke ich weiss im allgemeinen was ich tue. Das einzige was da optimiert gehört ist dass man a) sowieso keinen vector<string> verwenden sollte weil das schonmal ganz grob langsam ist, und b) den "buffer" irgendwo als member anlegen sollte, damit der nicht ständig neu angelegt und reallokiert wird.
    Ansonsten ist single-pass im Allgemeinen schneller als multi-pass... sollte auch logisch sein.



  • ach jetzt seh ich das: du nimmst gleich die backslashes aus dem string mit weg... daran hab ich nicht gedacht.



  • Jupp 🙂
    Wobei ich natürlich nicht behaupten will dass meine Version "optimal" sei -- nur komplett sinnlos kopiere ich auch nicht Daten durch die Gegend. Sozusagen 🙂



  • Irgendwie hatte ich wohl nen Blackout beim schreiben, hätte mir eigentlich auffallen sollen, dass man die Bedingung ja direkt dran hängen kann ohne irgendwelche unnötigen Vergleiche vorzunehmen.

    Ich habs jetzt so:

    else if( *it == '\"' ) //beginning of a string
    {
    	std::string::const_iterator begin( ++it );
    
    	while( (it != end && *it != '\"') ||
    		( *it == '\"' && *( it - 1 ) == '\\' ) )
    	{								
    		++it;
    	}
    
    	argVec.push_back( std::string( begin, it++ ) );
    
    	boost::algorithm::replace_all( argVec[ argVec.size() - 1 ], "\\\"", "\"" );
    }
    

    Irgendwelche aufwändigen Techniken sind hier wirklich nicht angebracht, ich hab so einen Parser der robust und fehlertorelant genug ist in knapp 50Zeilen mit viel whitespace.
    Wenn es die Mühe wert wäre, würde ich einen richtigen Parser schreiben, der escape Sequenzen auch richtig beherrscht (tut der hier ja nicht).

    Danke c.rackwitz für den "Wink mit dem Zaunpfahl".



  • Ich möchte nur anmerken dass es nicht immer gut ist ultra-kompakten Code zu haben. Ich habe lieber Code der irgendwo zwischendrin ist (auf "kompakt" bezogen), dafür halbwegs gut lesbar und halbwegs gut verständlich, als Code der zwar ultra kompakt ist, dafür nicht so einfach verständlich.

    Klar ist es gut "übersichtlich" zu programmieren, aber man kann es auch übertreiben finde ich. Ich lese lieber Code auf 2 Bildschirmseiten wo ich in 30 Sekunden verstehe was abgeht (und wie), als Code auf 1/2 Bildschirmseite wo ich nach 2 Minuten immer noch nicht 100% sicher bin warum das eigentlich funktioniert, bzw. im Extremfall: was der Code eigentlich tut, bzw. wie. Fehler Suchen in so einem Code macht garkeinen Spass.

    Die "while Schleife mit gleich allem in der Bedingung" ist für mich schon nichtmehr Grenzwertig, sondern "zu kompakt". z.B. ist nicht auf den ersten Blick ersichtlich dass das "*(it - 1)" OK ist. Es ist zwar OK (it wird ja vorher incrementiert), aber man kann ohne weiteres mal ein paar Sekunden brauchen bis man das überzuckert.



  • in dem falle ist ein kommentar angebracht, um die verständlichkeit zu wahren



  • r0nny schrieb:

    in dem falle ist ein kommentar angebracht, um die verständlichkeit zu wahren

    Die Verständlichkeit, ja. Aber es geht auch darum ob ich einem Code "trauen" kann. Und da hilft auch das Kommentar nix. Da muss ich dann alle Randbedingungen durchgehen und ... ach, ich finde das einfach lästig mit so kompaktem Code 😉



  • Die Kommentare hab ich rausgenommen.
    Original sieht es so aus:

    while( (it != end && *it != '\"') ||	//a quote char start
    																//skipping
    	( *it == '\"' && *( it - 1 ) == '\\' ) )//an escaped quote char
    {													//continue skipping
    				++it;
    }
    

    Die Formatierung passt jetzt halt nimmer, deswegen hab ich sie auch rausgenommen.

    Im Prinzip ist es aber sehr einfach zu verstehen, da durch das Oder zwei unterschiedliche Bedingungen getrennt werden, kann man sie sich auch getrennt
    anschauen und dann seh ich da kein(e) Problem(e) beim Verständnis.
    Beide Bedingungen für sich sind doch sehr einfach zu verstehen.

    Allerdings fällt mir gerade ein Fehler auf, die Bedingung it!=end muss für beide Kriterien gelten.

    Und um das -1 kommt man nicht herum um das Zeichen das davor liegt zu prüfen.
    Der Iterator wird ikrementiert, damit das anfängliche " nicht im string enthalten ist.



  • Ich weiß nicht ... bisher hat mich nichts überzeugt. Was ist eigentlich gegen einen stateful parser einzuwenden? Ist zwar länger, folgt aber einem festen Schema und ist beliebig erweiterbar.

    bool parse_quoted_string(Iterator begin, Iterator end)
    {
        enum states { start, normal, quoted, complete, failure };
    
        states state = start;
    
        for (Iterator it = begin; it != end and state != failure; ++it) {
            switch (state) {
                case start:
                    if (*it == '"') state = normal;
                    else            state = failure;
                    break;
                case normal:
                    if (*it == '\\')     state = quoted;
                    else if (*it == '"') state = complete;
                    else                 /* Zeichen verarbeiten */;
                    break;
                case quoted:
                    if (*it != '"')
                        state = failure;
                    else {
                        state = normal;
                        /* Zeichen verarbeiten */
                    }
                    break;
                case complete:
                    state = failure;
                    break;
            }
        };
    
        return state == complete;
    }
    

Anmelden zum Antworten