Parsen von nested structs mit Boost Spirit



  • Hallo,

    ich versuche gerade eine etwas kompliziertere Textdatei zu parsen. Ich habe gesehen, dass Boost bereits einen Parser hat, den ich dann auch gleich benutzen wollte.

    Ich muss gleich vorweg schicken, dass ich ein kompletter Neuling im Bereich Boost Spirit und Boost Fusion bin, was die Sache nicht unbedingt einfacher macht. Ich habe bisher immer andere Module benutzt.

    Ok, hier das Problem: Ich versuche folgende Datei zu parsen:

    class HkmTree Format: v2.1
    #nodes:  9
    #leaves: 8
    
    <HkmParams 
    	branchFactor:                8
    	maxTreeDepth:                1
    	desiredNumSamplesPerCluster: 15000000
    	minNumSamplesPerCluster:     15000000
    	numClusteringRuns:           1
    	standardization:             0
    	meanSubtraction:             0
    	noInternalLeafStats:         0
    	termCrit.maxCount:           1000
    	termCrit.epsilon:            1e-006
    	termCrit.type:               3
    >
    

    Das ist nicht die vollständige Datei. Ich versuche mich nur schrittweise an das Problem heranzutasten. Ich versuche erst einmal Teilgrammatiken an Teilen der Datei auszuprobieren bevor ich dann alles zusammenbaue.

    Die geparsten Daten sollen dann am Ende in einem struct stehen. Hier sind die structs die bisher existieren:

    struct HkmHeader {
    	std::string version;
    	int numNodes;
    	int numLeaves;
    };
    
    struct HkmParams {
    	int branchFactor;
    	int maxTreeDepth;
    	int desiredNumSamplesPerCluster;
    	int numClusteringRuns;
    	int standardization;
    	int meanSubtraction;
    	int noInternalLeafStats;
    	int termCritMaxCount;
    	float termCritEpsilon;
    	int termCritType;
    };
    
    struct HkmFile {
    	HkmHeader header;
    	HkmParams params;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(
    	HkmHeader,
    	(std::string, version)
    	(int, numNodes)
    	(int, numLeaves)
    )
    
    BOOST_FUSION_ADAPT_STRUCT(
    	HkmParams,
    	(int, branchFactor)
    	(int, maxTreeDepth)
    	(int, desiredNumSamplesPerCluster)
    	(int, numClusteringRuns)
    	(int, standardization)
    	(int, meanSubtraction)
    	(int, noInternalLeafStats)
    	(int, termCritMaxCount)
    	(float, termCritEpsilon)
    	(int, termCritType)
    )
    
    BOOST_FUSION_ADAPT_STRUCT(
    	HkmFile,
    	(HkmHeader, header)
    	(HkmParams, params)
    )
    

    Die Grammatik ist folgendermaßen implementiert:

    template<typename Iterator, typename Skipper>
    struct HkmGrammar
    	: qi::grammar<Iterator, HkmFile(), Skipper>
    {
    	qi::rule<Iterator, HkmFile(), Skipper> start;
    	qi::rule<Iterator, HkmHeader(), Skipper> hkmHeader;
    	qi::rule<Iterator, HkmParams(), Skipper> hkmParams;
    
    	qi::rule<Iterator, std::string(), Skipper> charstr;
    
    	HkmGrammar()
    		: HkmGrammar::base_type(start)
    	{
    		charstr = +qi::char_;
    
    		hkmHeader = qi::lit("class") >> qi::lit("HkmTree") >> qi::lit("Format:") >> charstr ;
    		hkmParams = qi::lit("<HkmParams") >> 
    			qi::lit("branchFactor:") >> qi::int_ >>
    			qi::lit("maxTreeDepth:") >> qi::int_ >>
    			qi::lit("desiredNumSamplesPerCluster:") >> qi::int_ >>
    			qi::lit("minNumSamplesPerCluster:") >> qi::int_ >>
    			qi::lit("numClusteringRuns:") >> qi::int_ >>
    			qi::lit("standardization:") >> qi::int_ >>
    			qi::lit("meanSubtraction:") >> qi::int_ >>
    			qi::lit("noInternalLeafStates") >> qi::int_ >>
    			qi::lit("termCrit.maxCount") >> qi::int_ >>
    			qi::lit("termCrit.epsilon") >> qi::float_ >>
    			qi::lit("termCrit.type") >> qi::int_ >>
    			qi::lit(">");
    		start = hkmHeader >> hkmParams;
    	}
    };
    

    Ich habe das Programm zunächst einmal nur mit der Regel "hkmHeader" kompiliert. Da funktioniert alles einwandfrei. Das struct HkmHeader wird korrekt befüllt. Jetzt habe ich die Regel "hkmParams" als erste Erweiterung eingebaut und ich bekomme seltsame Fehlermeldungen vom Compiler:

    Fehler	1	error C2903: "apply": Symbol ist weder eine Klassen-template noch eine Funktions-template.	C:\Libs2012\boost_1_53_0\boost\fusion\container\vector\convert.hpp	25
    Fehler	2	error C2027: Verwendung des undefinierten Typs "boost::fusion::detail::as_vector<size>"	C:\Libs2012\boost_1_53_0\boost\fusion\container\vector\convert.hpp	25
    Fehler	3	error C2143: Syntaxfehler: Es fehlt ';' vor '<'	C:\Libs2012\boost_1_53_0\boost\fusion\container\vector\convert.hpp	25
    Fehler	4	error C4430: Fehlender Typspezifizierer - int wird angenommen. Hinweis: "default-int" wird von C++ nicht unterstützt.	C:\Libs2012\boost_1_53_0\boost\fusion\container\vector\convert.hpp	25
    Fehler	5	error C2039: 'type': Ist kein Element von '`global namespace''	C:\Libs2012\boost_1_53_0\boost\fusion\container\vector\convert.hpp	25
    Fehler	6	error C2238: Unerwartete(s) Token vor ';'	C:\Libs2012\boost_1_53_0\boost\fusion\container\vector\convert.hpp	26
    Fehler	7	error C2039: 'type': Ist kein Element von 'boost::mpl::eval_if<C,F1,F2>'	C:\Libs2012\boost_1_53_0\boost\spirit\home\support\attributes.hpp	736
    Fehler	8	error C3203: "type": Die nicht spezialisierte template-Klasse kann nicht als template-Argument für den template-Parameter "T" verwendet werden, ein realer Typ wurde erwartet.	C:\Libs2012\boost_1_53_0\boost\spirit\home\qi\operator\sequence_base.hpp	59
    Fehler	9	error C2955: "boost::type": Für die Verwendung der template-Klasse ist eine template-Argumentliste erforderlich.	C:\Libs2012\boost_1_53_0\boost\spirit\home\qi\operator\sequence_base.hpp	59
    Fehler	10	error C2955: "boost::spirit::traits::strip_single_element_vector": Für die Verwendung der template-Klasse ist eine template-Argumentliste erforderlich.	C:\Libs2012\boost_1_53_0\boost\spirit\home\qi\operator\sequence_base.hpp	59
    Fehler	11	error C3203: "type": Die nicht spezialisierte template-Klasse kann nicht als template-Argument für den template-Parameter "T" verwendet werden, ein realer Typ wurde erwartet.	C:\Libs2012\boost_1_53_0\boost\spirit\home\qi\operator\sequence_base.hpp	81
    Fehler	12	error C2955: "boost::type": Für die Verwendung der template-Klasse ist eine template-Argumentliste erforderlich.	C:\Libs2012\boost_1_53_0\boost\spirit\home\qi\operator\sequence_base.hpp	81
    

    Meine (zugegebenerweise nicht wirklich vertrauenswürdige) Intuition sagt mir, dass es irgendein Problem bei den nested Structs gibt. Kann das jemand bestätigen und evtl. vielleicht einen Lösungsvorschlag machen?

    Danke



  • Compuholic schrieb:

    ich versuche gerade eine etwas kompliziertere Textdatei zu parsen.

    So kompliziert ist die auch wieder nicht, hier die Version mit dem normalen istream:

    std::istream& operator>>(std::istream& in, HkmHeader& f)
    {
      return in >> lit("class HkmTree Format:") >> f.version
                >> lit("#nodes:") >> f.numNodes
                >> lit("#leaves:") >> f.numLeaves;
    }
    
    std::istream& operator>>(std::istream& in, HkmParams& f)
    {
      int dummy;
      return in >> lit("<HkmParams")
                >> lit("branchFactor:") >>                f.branchFactor
                >> lit("maxTreeDepth:") >>                f.maxTreeDepth 
                >> lit("desiredNumSamplesPerCluster:") >> f.desiredNumSamplesPerCluster 
                >> lit("minNumSamplesPerCluster:") >>     dummy 
                >> lit("numClusteringRuns:") >>           f.numClusteringRuns
                >> lit("standardization:") >>             f.standardization
                >> lit("meanSubtraction:") >>             f.meanSubtraction
                >> lit("noInternalLeafStats:") >>         f.noInternalLeafStats
                >> lit("termCrit.maxCount:") >>           f.termCritMaxCount
                >> lit("termCrit.epsilon:") >>            f.termCritEpsilon
                >> lit("termCrit.type:") >>               f.termCritType
                >> lit(">");
    }
    
    std::istream& operator>>(std::istream& in, HkmFile& f)
    {
      return in >> f.header >> f.params;
    }
    

    Alles was du brauchst, ist die Klasse lit:

    struct lit {
      const char *str;
      lit(const char *str) : str(str) {}
    };
    std::istream& operator>>(std::istream& in, lit l)
    {
      char c; in >> c;
      while (in && *l.str) {
        if (*l.str != c) in.setstate(std::ios_base::failbit);
        if (isspace(*++l.str))
          in >> c, l.str++;
        else
          c = in.get();
      }
      return in;
    }
    


  • Vollständiges Beispiel: http://ideone.com/7f7mEH



  • Du versuchst in der Regel eine Zahl mehr zu parsen, als im Struct enthalten ist. minNumSamplesPerCluster fehlt in HkmHeader. Das wird dazu führen, dass fusion_foo_type<HkmHeader, 10>::apply (ich bin jetzt aus dem Stand nicht sicher, wie Fusion die Typvorlage nennt, die es da benutzt, aber etwas in der Art ist das) noch definiert ist, fusion_foo_type<HkmHeader, 11>::apply aber nicht mehr (weil fusion_foo_type<HkmHeader, 11> keine Spezialisierung mehr hat).



  • @seldon:

    Danke, das Du mich darauf aufmerksam gemacht hast. Ich habe das entsprechende Feld im struct und dem Boost-Makro hinzugefügt. Leider lag es wohl nicht daran. Der Fehler besteht weiterhin unverändert.

    @lit:
    Danke für Deine Anwort. Leider hilft mir das wenig. Wie gesagt: Der Datei-Ausschnitt den ich gepostet habe, ist nur ein kleiner Teil der zu parsenden Datei. Und da die Datei später Baumstrukturen und XML-ähnliche Attribut-Wert Paare (die zum Teil auch noch optional sein können) enthält, wird es extrem umschön das mit ad-Hoc Lösungen zu machen. Deshalb versuche ich das ja auch mit einem echten Parser.


Anmelden zum Antworten