Boost spirit, Parserprobleme



  • Hallo,
    ich möchte mit Boost Spirit bzw. Qi txt-Dateien des folgenden Formats einlesen:

    //***************************************************************************
    [TriggerEventStrings]
    
    // Special events not defined in common.j,
    // handled internally by the editor
    MapInitializationEvent="Map Initialization"
    MapInitializationEvent="Map initialization"
    MapInitializationEventHint=
    
    ...
    
    [TriggerConditionStrings]
    
    OperatorCompareBoolean="Boolean Comparison"
    OperatorCompareBoolean=~Value," ",~Operator," ",~Value
    
    ...
    [Meine Section] // ein Kommentar
    HansWurst=2323 Gustav,Heinz // ein Kommentar
    

    Allgemein gibt es sogenannte Sections, die mit

    [Name]
    

    eingeleitet werden.
    Darauf folgen beliebig viele Wertepaare. Die Werte können alles bis auf Zeilenumbrüche und Kommentare enthalten. Sprich, der Zeilenumbruch oder Kommentar schließt den Wert ab. Für den Schlüssel des Werts lasse ich momentan einfache Ascii-Zeichenketten zu, was anscheinend ausreicht.

    Im Skipper wollte ich keine Zeilenumbrüche überspringen, lediglich andere Whitespaces und Kommentare, da die Zeilenumbrüche ja etwas mit der Semantik der Ausdrücke zu tun haben.

    Hier mal der Code:

    template<typename Iterator>
    struct CommentSkipper : public qi::grammar<Iterator> {
    
    	qi::rule<Iterator> skip;
    
    	CommentSkipper() : CommentSkipper::base_type(skip, "PL/0")
    	{
    		skip = ascii::blank | lit("//") >> *(standard::char_ - qi::eol) >> qi::eol;
    	}
    };
    
    template <typename Iterator, typename Skipper = CommentSkipper<Iterator> >
    struct KeyValueSquence : qi::grammar<Iterator, Txt::Pairs(), Skipper>
    {
    	//Txt::Pairs::value_type
    	qi::rule<Iterator, Txt::Pairs(), Skipper> query; // NOTE first rule used as parameter for base_type does always need the skipper type of the grammar
    	qi::rule<Iterator, std::pair<string, string>(), Skipper> pair;
    	qi::rule<Iterator, string()> key, value;
    
    	KeyValueSquence() : KeyValueSquence::base_type(query)
    	{
    		query =  pair > *(pair); // use only > for backtracking
    		pair  =  +qi::eol > key > lit('=') > -value; // -('=' >> value)
    		key   =  standard::char_("a-zA-Z_") > *standard::char_("a-zA-Z_0-9");
    		value = +(standard::char_ - qi::eol); // values can be empty or all characters except eol which indicates the and of the value
    	}
    };
    
    /*
    typedef std::istreambuf_iterator<byte> IteratorType;
    typedef boost::spirit::multi_pass<IteratorType> MultiPassIteratorType;
    
    template struct KeyValueSquence<MultiPassIteratorType>;
    */
    
    template <typename Iterator, typename Skipper = CommentSkipper<Iterator> >
    struct SectionRule : qi::grammar<Iterator, Txt::Section(), Skipper>
    {
    	qi::rule<Iterator, Txt::Section(), Skipper> query;
    	qi::rule<Iterator, string()> name;
    	qi::rule<Iterator, Txt::Pairs(), Skipper> entries;
    
    	KeyValueSquence<Iterator, Skipper> keyValueSequence;
    
    	SectionRule() : SectionRule::base_type(query)
    	{
    		query =  name > -entries;
    		name  =  lit('[') > standard::char_("a-zA-Z_") > *standard::char_("a-zA-Z_0-9") > lit(']');
    		entries = keyValueSequence;
    	}
    };
    
    //template struct SectionRule<MultiPassIteratorType>;
    
    template <typename Iterator>
    bool parse(Iterator first, Iterator last, Txt::Sections &sections)
    {
    	SectionRule<Iterator> sectionGrammar;
    	CommentSkipper<Iterator> commentSkipper;
    	std::vector<Txt::Section> tmpSections;
    
    	bool r = boost::spirit::qi::phrase_parse(
    	first,
    	last,
    	(*qi::eol > -(sectionGrammar > *(+qi::eol > sectionGrammar)) > *qi::eol),
    	// comment skipper
    	commentSkipper,
    	tmpSections //sections store into "sections"!
    	);
    
    	if (first != last) // fail if we did not get a full match
    	{
    		return false;
    	}
    
    	// TODO temporary workaround, add sections directly from heap to vector
    	BOOST_FOREACH(std::vector<Txt::Section>::const_reference ref, tmpSections) {
    		std::auto_ptr<Txt::Section> s(new Txt::Section());
    		s->name = ref.name;
    		s->entries = ref.entries;
    		sections.push_back(s);
    	}
    
    	return r;
    }
    

    Der Header:

    class Txt : public Format
    {
    	public:
    		typedef std::map<string, string> Pairs;
    
    		struct Section
    		{
    			string name;
    			Pairs entries;
    		};
    
    		typedef boost::ptr_vector<Section> Sections;
    
    		Sections& sections();
    		const Sections& sections() const;
    		/**
    		 * Returns all key value entries of section \p section.
    		 * \param section An existing section ([bla]) of the TXT file.
    		 * \throws Exception Throws an exception if section \p section does not exist.
    		 * \note Since sections are stored in a vector for more efficency while reading a TXT file this search has a complexity of O(n).
    		 */
    		const Pairs& entries(const string section) const;
    
    		virtual std::streamsize read(InputStream &istream) throw (Exception);
    		virtual std::streamsize write(OutputStream &ostream) const throw (Exception);
    
    	private:
    		Sections m_sections;
    };
    

    Beim Parsen gibt es mindestens zwei Probleme. Die letzte Zeile wird immer als Fehler markiert, obwohl ich theoretisch Zeilenumbrüche nach den Sections zulasse.
    Kommentare, die am Ende eines Wertepaars oder einer Section stehen, schlucken den Zeilenumbruch, der für das nächste Wertepaar benötigt wird.

    Außerdem weiß ich nicht, wie ich effizient (vielleicht mit Phoenix?) Sections in einen boost::ptr_vector schreiben kann. Daher muss ich das am Ende mit einer for-Schleife machen und alle Werte kopieren.

    Meine Fragen sind also konkret:

    • Wie erreiche ich, dass der letzte Zeilenumbruch/die letzte Zeile kein Fehler ist?
    • Wie kann ich Kommentare schlucken und trotzdem gewährleisten, dass zwischen den Wertepaaren und auch der Section mindestens ein Zeilenumbruch steht?
    • Wie kann ich boost::ptr_vector effizienter füllen, möglichst ohne Kopien zu machen?

    Zudem habe ich in einigen Beispielen qi::omit gesehen. Macht das den Parser schneller?

    Wäre für jede Hilfe echt dankbar, da ich noch nicht so lange was mit boost::spirit mache 🙂

    Gruß
    Barade


Log in to reply