dickes designproblem



  • ich arbeite ja mit boost::spirit, und ich wollte das konzept irgendwie mit klassen in einklang bringen, aber irgendwie scheitere ich ziemlich, darüber sind auchs chon ein paar tränen und jede menge schweiß geflossen(ergo ich hab darüber aufm hometrainer nachgedacht :D) das problem sind keine fehlermeldungen, sondern dass ich es nicht schaffe die instanz einer klasse an die "informationen verarbeitende stellen" zu bringen,dh klassen sind nicht gut mit spirit in einklang zu bringen, aber zuerst etwas unausgegorenen code:

    //ich brauche hier die functoren, da spirit keine methoden annimmt.
    template<class Type>
    class sendChar{
    	private:
    		typedef void (Type::*function)(char);
    		Type& classPointer;
    		function functionPointer;
    		char sign;
    	public:
    		sendChar(Type& instance,function pointer,char signToSend):classPointer(instance),functionPointer(pointer),sign(signToSend){}
    		//der op ist so von spirit vorgegeben, deshalb dieser umweg über den ctor
                    void operator()(char const*, char const*)const{
    			(classPointer.*functionPointer)(sign);
    		}
    
    };
    template<class Type,class numberType>
    class SendNumber{
    	private:
    		typedef void (Type::*function)(numberType);
    		Type& classPointer;
    		function functionPointer;
    	public:
    		SendNumber(Type& instance,function pointer):classPointer(instance),functionPointer(pointer){}
                    void operator()(numberType number)const{
    			(classPointer.*functionPointer)(number);
    		}
    };
    //hier ist meine "verarbeitende klasse"
    class Interpreter{
    public:
    	void sendSign(char a){
    		//hier werden die werte bearbeitet
    	}
    	void sendDouble(double value){
    		//hier werden die doubles verarbeitet
    	}
    };
    
    //die formd er klasse sit von spirit so vorgegeben, definition ist der einzige benutzte teil, ich hab deshalb keine chance von aussen irgendeine instanz reinzuschmuggeln
    struct calculation:public boost::spirit::grammar<calculation>{
    	template <typename ScannerT>
    	struct definition{
    		Interpreter interpreter;
    		boost::spirit::subrule<0> real;
    
    		boost::spirit::subrule<1> expression;
    		boost::spirit::subrule<2> term;
    		boost::spirit::subrule<3> group;
    		boost::spirit::subrule<4> summand;
    
    		boost::spirit::rule<ScannerT> first;
    		definition(calculation const& self){
                            //die grammatik ansich
    			first=
    			(
    				expression=term|real,
    				term=
    					 summand
    					 >>*(		('+'>>summand)[sendChar<Interpreter>(interpreter,&Interpreter::sendSign,'+')]
    //hier wird der functor dann benutzt, wenn der parser bis zum op[] erfolgreich war, wird der functor aufgerufen
    						|		('-'>>summand)[sendChar<Interpreter>(interpreter,&Interpreter::sendSign,'-')]
    						),
    				summand=
    						(real|group)
    						>>*(	('*'>>(real|group))[sendChar<Interpreter>(interpreter,&Interpreter::sendSign,'*')]
    						|		('/'>>(real|group))[sendChar<Interpreter>(interpreter,&Interpreter::sendSign,'/')]
    						),
    				group='('>>term>>')',
    
    				real=boost::spirit::real_p[SendNumber<Interpreter,double>(interpreter,&Interpreter::sendDouble)]
    			);
    		}
    		boost::spirit::rule<ScannerT> const& start() const{
    			return first;
    		}
                    //ende grammatik
    	};
    };
    

    so, ein ziemlich wustes undurchdringliches stück code, verzeiht mir bitte die form des aufrufs der functoren, aber ich hatte da noch keine zeit eine passende funktion zu basteln, der code ist eigentlich nur sehr experimentell^^
    so wie der code da steht funktioniert er, das problem ist nur, dass ich keine möglichkeit habe über einen ctor einen interpreter/functoren einzumogeln. kennt jemand von euch noch eine halbwegs anständige möglichkeit?

    ich will nicht auf einfache funktionen zurückgreifen müssen, da ich ehrlich gesagt keine ahnung hätte, wie ich das anstellen sollte.

    ps: bitte interpretiert das als hilfeschrei, ich sitz an dem problem jetzt schon seit 3 tagen, und bin keinen milimeter weitergekommen.



  • //die formd er klasse sit von spirit so vorgegeben, ..

    Das stimmt so nicht.
    http://boost.org/libs/spirit/doc/grammar.html

    Die Klasse calculator darf also schon
    Methoden, Variablen, etc.. haben.

    Somit kannst die Funktoren ja an den Konstruktor von
    calculator übergeben. Ein kleines weiteres Problem
    wäre dann, dass du von definition ohne weiteres keinen
    Zugriff darauf hättest. Allerdings bekommst eh eine
    Referenz self auf die umhüllende Klasse.

    Die ganzen Funktoren könntest aber auch ohne weiteres
    loswerden, wenn du boost::function und boost::bind
    benutzt.

    Hier mal ein Lösungsansatz, musst halt noch auf dein
    Problem erweitern, kenn dafür boost::spirit nicht gut
    genug.

    #include <boost/spirit/utility/grammar_def.hpp>
    #include <boost/spirit/core.hpp>
    #include <boost/function.hpp>
    #include <boost/bind.hpp>
    #include <iostream>
    
    class Interpreter
    {
    	private:
    		// ...	
    
    	public:
    		void pushChar(char)
    		{ /* Hier den übergebenen Operator verarbeiten */ }
    
    		void pushFloat(float)
    		{ /* Hier die übergeben Zahl verarbeiten */	   }
    
    		float getResult()
    		{ /* Hier das Ergebniss zurückliefern */          }
    };
    
    template<class InterpreterT>
    class Calculator : public boost::spirit::grammar<Calculator<InterpreterT> >
    {
    	private:
    		InterpreterT& interpreter_;
    		boost::function<void (char) > pushChar;
    		boost::function<void (float)> pushFloat;		
    
    	public:
    		Calculator(InterpreterT& interpreter)
    			: interpreter_(interpreter),
    			  pushChar (boost::bind(&InterpreterT::pushChar , interpreter_, _1)),
    			  pushFloat(boost::bind(&InterpreterT::pushFloat, interpreter_, _1))
    		{	}
    
    		template<class ScannerT>
    		class definition
    		{
    			private:
    				boost::function<void (char) > pushChar;
    				boost::function<void (float)> pushFloat;
    
    				boost::spirit::rule<ScannerT> op, term;
    
    			public:
    				definition(const Calculator& self)
    					: pushChar (self.pushChar ),
    					  pushFloat(self.pushFloat)
    				{
    					using boost::spirit::ch_p;
    					using boost::spirit::real_p;
    
    					op   = ch_p('+')[pushChar] | ch_p('-')[pushChar];
    					term = real_p[pushFloat] >> *(op >> real_p[pushFloat]);
    				}
    
    				const boost::spirit::rule<ScannerT>& start() const
    				{	return term; }
    		};
    };
    
    bool parse_string(const char* str, Interpreter& interpreter)
    {
    	static Calculator<Interpreter> calc(interpreter);
    
    	return boost::spirit::parse(str, calc, boost::spirit::space_p).full;
    }	
    
    int main()
    {
    	std::string line;
    	Interpreter interpreter;
    
    	while(std::getline(std::cin, line)	
    	   && parse_string(line.c_str(), interpreter))
    	{
    		std::cout << "result: " << interpreter.getResult() << std::endl;
    	}
    }
    

    Hoffe, es hat geholfen 🙂
    Tankian



  • op = ch_p('+')[pushChar] | ch_p('-')[pushChar];

    hmm sicher ginge das so auch mit boost::function, aber ich könnte dann die automatische konvertierung von a+b in a b + vergessen, das war eigentlich der sinn des ganzen :), und durch die andere stellung ist nur der op()(char const*,char* const)const erlaubt, und boost::function unterstützt keine unterschiede zwischen dem typ der übergebenen funktion und op().

    aber die sache mit dem ctor ist richtig, hab ich ganz übersehen, ich trottel^^
    ich war gestern späten abend schon fast soweit, mir über policies die instanz als singleton zu holen^^



  • Aus deiner Antwort werd ich nicht gerade schlau :f

    automatische konvertierung von a+b in a b + vergessen

    Falls ich das jetzt richtig verstanden habe, meins das a b + eingegeben werden
    und richtig verarbeitet werden kann ?

    a b + sollte sowieso nicht geschluckt werden (zumindestens bei der Grammatik
    aus meinem Beispiel) und wenn du das unbedingt in der Form a b + haben willst,
    kannst das ja auch noch bei späterer Verarbeitung machen.

    und durch die andere stellung ist nur der op()(char const*,char* const)const erlaubt

    Was für ein operator()(const char*, const char*) const ?

    Falls du meinst, dass du mehr als nur einzelne Zeichen, Zahlen sendest, das
    geht doch genauso mit boost::function.

    class Interpreter
    {
        // ...
        public:
            void pushStrings(const char*, const char*)
            {        }
    
            // ...
    };
    
    template<class InterpreterT>
    class Calculator : public boost::spirit::grammar<Calculator<InterpreterT> >
    {
        private:
            // ...
            boost::function<void (const char* const char*)> pushStrings;
    
        public:
            Calculator(Interpreter& interpreter)
                : // ...
                  pushStrings(boost::bind(&InterpreterT::pushStrings, interpreter_, _1, _2))
            {    }
    
            template<class ScannerT>
            class definition
            {
                 private:
                      // ...
                      boost::function<void (const char*, const char*)> pushStrings;
    
                      boost::spirit::rule<ScannerT> op, term, expression;
    
                 public:
                     definition(const Calculator& self)
                         : // ...
                           pushStrings(self.pushStrings)
                     {  
                         op   = ch_p('+') | ch_p('-');
                         term = real_p >> *(op >> real_p);
                         expression = term[pushStrings];
                     }
    
                     const boost::spirit::rule<ScannerT>& start() const
                     {   return expression; }
            };
    };
    

    Wobei ich das aber auch nicht gerade so gut finde, denn dann muss man sich
    selbst bei der Verarbeitung drum kümmern, den String zu zerlegen in dei
    einzelnen Ausdrücke. Darum würd ichs lieber gleich zerlegt senden.

    Schätze mal ich hab dich aber doch wohl falsch verstanden 🙄
    Tankian



  • aus meiner antwort wird man auch nur schlau, wenn man sich etwas mit dem parsen von strings auseinandergesetzt hat(das drachenbuch gibt aufschluss)

    der string a+b kann nicht gut interpretiert werden, deshalb wandelt der parser das für nachfolgende verarbeitungsschritte in a b + um. diese form nennt man Reverse Polish Notation (RPN).

    Was für ein operator()(const char*, const char*) const

    dies ist die normale form des op() bzw einer funktion, die boost::spirit im op[] schluckt, jediglich wenige einzelparser unterstützen andere funktionen(real_p void (double), ch_p void(char) usw).

    eine gruppe von parsern bzw eine regel unterstützt standardmäßig nur funktionen der von mir genannten form.

    Wobei ich das aber auch nicht gerade so gut finde, denn dann muss man sich
    selbst bei der Verarbeitung drum kümmern, den String zu zerlegen in dei
    einzelnen Ausdrücke. Darum würd ichs lieber gleich zerlegt senden.

    du brauchst einzelne strings, wenn du zb ganze ausdrücke wie variablennamen parsen lassen willst.

    operator()(const char*, const char*) wurde von boost::spirit als unterstützter standardoperator genommen, da man wesentlich öfter strings als zeichen bzw zeichen ausgeben muss. das man diese funktionen misbrauchen kann, um zb fehlermeldungen auszugeben,das ist sogar von spirit beabsichtigt:

    zb

    void error (const char*, const char*){
        cout<<"string stellt keine zahl dar";
    }
    //im parser
    real_p|eps_p[&error]
    

    eps_p stellt einen nullparser da(dh eps_p parst kein zeichen des strings und ruft nur error auf).



  • ok, das grundsätzliche problem der benutzbarmachung des parsers habe ich hinter mir, nun hatte ich ein bischen mehr zeit, mich um das desgin des mathe parsers zu beschäftigen, imho sieht mein ansatz so aus:

    zuerst wirkt der parser ansich seines amtes, mithilfe der grammatik teilt er den string in stücke, und schickt die stücke an einen interpreter.
    der interpreter interpretiert die stringteile, und macht daraus einzelne pakete

    zb wenn ein string '+' heisst, liefert der interpreter eine struktur, welche einen operator+ darstellt.

    im letzten schritt holt sich dann eine klasse "parseTree" die pakete ab, und baut damit einen baum, der bei bedarf vom progrmam ausgeführt werden kann.

    frage: ist das so praktikabel? ließe sich das prinzip auch hinterher noch erweitern(also zb auf typunterscheidung, variablen,if/else usw)? oder reicht das system grade mal für einfachste mathematische spielereien(also sowas wie 1+2*3+cos(4))


Anmelden zum Antworten