Codeschnipsel verbessern / verschönern



  • Ich möchte Funktionsparameter parsen, und der folgende Code gefällt mir nicht so besonders.

    Es geht darum den sauberer zu machen. Und evtl habe ich einen Fall übersehen, der hier durchschlüpft, obwohl er falsch ist, oder einen der nicht funktioniert, obwohl er stimmt.

    Variablennamen dürfen nur normale Buchstaben enthalten (bis jetzt)

    [unter dem code ist noch text]

    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <boost/regex.hpp>
    #include <boost/tokenizer.hpp>
    
    int main () // nur zum testen, soll später in eine Funktion 
    {
    	// Variablen dürfen nur buchstaben enthalten
    	std::string VmaskStr = R"([A-Za-z]+)";
    	boost::regex Varmask(VmaskStr);
    
    	/*
    		Valid: (typen und parameternamen (buchstaben only))
    			int abc, float b
    			*empty*
    			int AbD
    
    		Invalid:
    			int 2, float c
    			int 2a
    			int a2
    			int2 a
    			int a float b
    			int a, float b_
    			int a, float b char
    	*/
    
        auto a = std::begin(/* hier ein array von strings, den ich zum testen benutze, habe ich entfernt + die schleife */);
    	boost::tokenizer<> tok(*a);
    	unsigned int counter = 0;	// hiermit prüfe ich die Anzahl an Wörtern
    	for (boost::tokenizer<>::iterator i = tok.begin(); i != tok.end(); ++i) {
    		++counter;
    
    		boost::smatch match;		
    		if (!boost::regex_match(*i, match, Varmask)) {
    			std::cout << "[INVALID] ";
    		}
    
    		if (typeswitch)
    			std::cout << "TYPE: ";
    		else {
    			std::cout << "VARNAME: ";
    		}
    
    		std::cout << *i << "\n";
    		typeswitch = !typeswitch;
    	}
    	if (counter % 2) {
    		std::cout << "invalid amount of words\n";
    	}
    	if (counter != 0 && (counter / 2U) - 1U != std::count(a->begin(), a->end(), ',')) {
    		std::cout << "amount of ',' does not match the amount of words\n";
    	}
    
    	return 0;
    }
    

    Was mir nicht gefällt:

    • counter, ich wollte eigentlich die differenz der tokenizer pointer verwenden, aber da lande ich im Header von boost. "has no member named 'distance_to'"
    • Die boolesche Variable zum toggeln, ob typ oder vname, ich würde lieber paare bekommen

    EDIT: verdammt, der tokenizer schneidet ja auch Zeichen wie ";" gleich raus. :|



  • Gegenvorschlag:

    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/std_pair.hpp>
    
    #include <string>
    #include <utility>
    #include <vector>
    
    typedef std::vector<std::pair<std::string, std::string> > parameter_list;
    
    bool parse_parameter_list(std::string const &s, parameter_list &list) {
      using namespace boost::spirit::qi;
    
      std::string::const_iterator iter = s.begin();
    
      bool r = phrase_parse(iter, s.end(),
    
                            // --> Grammatik hier <--
                            -((lexeme[+alpha] >> lexeme[+alpha]) % ','),
    
                            space,
                            list);
    
      return r && iter == s.end();
    }
    
    void dump_parameter_list(parameter_list const &vec) {
      std::cout << "-------------\n";
      for(std::size_t i = 0; i < vec.size(); ++i) {
        std::cout << vec[i].first << ", " << vec[i].second << '\n';
      }
      std::cout << "-------------\n\n";
    }
    
    void check_parameter_list(std::string const &s) {
      parameter_list ls;
    
      if(parse_parameter_list(s, ls)) {
        dump_parameter_list(ls);
      } else {
        std::cout << "invalid!\n";
      }
    }
    
    int main() {
      check_parameter_list("int abc, float b");
      check_parameter_list("");
      check_parameter_list("int AbD");
    
      check_parameter_list("int 2, float c");
      check_parameter_list("int 2a");
      check_parameter_list("int a2");
      check_parameter_list("int2 a");
      check_parameter_list("int a float b");
      check_parameter_list("int a, float b_");
      check_parameter_list("int a, float b char");
    }
    


  • Das sieht supi aus.
    *nimmt sich kaffe und liest sich durch Spirit 2.5.2*.

    Ich werde wohl ein bisschen refactoring betreiben müssen 😃

    EDIT: Die boost Dokumentation ist richtig gut...

    EDIT: Was ich in einem der Beispiele gefunden habe:

    Try defining the roman numerals grammar in YACC or PCCTS. Spirit rules! 🙂

    haha

    EDIT: [lange pause] Bin jetzt bei lexeme

    EDIT: Fertig! *ding* 😃 - Wieder etwas mehr, das ich kann

    EDIT: Ich schreibe gerade den kompletten Funktionsparser um. Ich poste das Ergebnis, wenn es soweit ist.



  • Ok Ich habe nun den Funktionsparser neu geschrieben mittels boost::spirit::qi
    Kritik erwünscht.

    #include <iostream>
    #include <string>
    #include <vector>
    
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/std_pair.hpp>
    #include <boost/spirit/include/phoenix_core.hpp>
    #include <boost/spirit/include/phoenix_operator.hpp>
    #include <boost/spirit/include/phoenix_stl.hpp>
    #include <boost/spirit/include/phoenix_fusion.hpp>
    #include <boost/spirit/include/phoenix_object.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    
    namespace QiSubroutines {
        namespace qi = boost::spirit::qi;
        namespace ascii = boost::spirit::ascii;
        namespace fusion = boost::fusion;
        namespace phoenix = boost::phoenix;
    
        typedef std::vector <std::pair <std::string, std::string> > paramContainerType;
    
        struct SubroutineBase {
            std::string name;
            paramContainerType parameters;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(
        QiSubroutines::SubroutineBase,
        (std::string, name)
        (QiSubroutines::paramContainerType, parameters)
    )
    
    namespace QiSubroutines {
        template <typename iterator_type>
        struct subroutine_parser : qi::grammar <iterator_type, SubroutineBase(), ascii::space_type>
        {
            subroutine_parser(): subroutine_parser::base_type(start, "subroutine")
            {
                using qi::lexeme;
                using ascii::char_;
                using qi::alpha;
                using qi::space;
                using qi::on_error;
                using qi::fail;
    
                using phoenix::at_c;
                using phoenix::push_back;
                using namespace qi::labels;
    
                using phoenix::construct;
                using phoenix::val;
    
                valid_text %= lexeme[+alpha];
    
                argpair =
                       lexeme[+alpha            [at_c<0>(_val) += _1]]
                    >  lexeme[+alpha            [at_c<1>(_val) += _1]]
                    >> -char_(',')
                ;
    
                start =
                       valid_text               [at_c<0>(_val) = _1]
                    >  char_('(')
                    >> *argpair                 [phoenix::push_back(at_c<1>(_val), _1)]
                    >  char_(')')
                    >  char_(';')
                ;
    
                start.name("qi_subroutine");
                argpair.name("argpair");
                valid_text.name("valid_text");
    
                on_error<fail>
                (
                    start ,
                    std::cout   << val("Error in subroutine parser - expecting: ")
                                << _4
                                << val( " here: \"")
                                << construct<std::string> (_3, _2)
                                << val("\"\n")
                 );
            }
    
            qi::rule <iterator_type, std::string(), ascii::space_type> valid_text;
            qi::rule <iterator_type, std::pair<std::string, std::string> (), ascii::space_type> argpair;
            qi::rule <iterator_type, SubroutineBase(), ascii::space_type> start;
        };
    }
    

    Main

    int main()
    {
        std::string str = "Name(int a, double b);";
    
        using QiSubroutines::SubroutineBase;
        using QiSubroutines::subroutine_parser;
        using boost::spirit::ascii::space;
        using boost::spirit::qi::phrase_parse;
    
        SubroutineBase base;
        bool ret = phrase_parse<std::string::const_iterator>
            (str.begin(),
            str.end(),
            subroutine_parser<std::string::const_iterator>(),
            space,
            base);
    
        if (!ret)
            std::cout << "dadümm\n";
        else {
            std::cout << "Name: " << base.name << "\n";
            for (auto i = base.parameters.begin(); i != base.parameters.end(); ++i) {
                std::cout << "parm pair: " << i->first << "-" << i->second << "\n";
            }
        }
    
        return 0;
    }
    

    EDIT: Die Lösung macht micht schon deutlich glücklicher.

    EDIT: boost::spirit::qi macht so viel fun, ich habe gleich mit den Schleifen weitergemacht 😉


Anmelden zum Antworten