Parsen...



  • Ich werkel grade an einer kleinen Konsole rum und überlege, wie man eingegebene Befehle am Besten parsen kann. Testweise lasse ich Argumente gerade außer Acht und habe meinen Befehls-Parser recht einfach aufgebaut, vereinfacht:

    char* input=GLOBAL.get(0); // Holt erses Argument aus Tabelle
    int i=0;
    
    while(input[i])
    {
        if(input[i]>='a' && input[i]<='z') { // Kleinbuchstaben -> Großbuchstaben
            input[i] = (input[i] - 32); }
        i++;
    }
    
         if (util::cmpstrn(input,"EXIT")) exit_flag=1;
    
    else if (util::cmpstrn(input,"ECHO")) cout << GLOBAL.getstrn(1) << "\n\n";
    
    else if (util::cmpstrn(input,"ABOUT")) {
    
             cout << "\n  Console\nVersion 0.1 Alpha"
                  << " (C) 2005. by BloodLord. All rights reserved."; }
    
    ...
    

    Jetzt finde ich es aber nicht sehr schön, eine endlos lange if/else if Liste zu machen die durchlaufen wird und bin auf der Suche nach einer besseren Möglichkeit, meine sinnlosen Einfälle sind dann Dinge wie:

    char **commands;
    
    ... // In commands kommt eine liste mit alle befehlen...
    
    int lalala(const char* input) {
        // ...denen alle eine nummer zugeordnet ist...
    
        for(int i=0; i<irgendwas; i++) {
    
            if(util::cmpstrn(input) return i; }
    
        return -1; }
    
    void hmhmhm() {
        int temp=lalala(GLOBAL.get(0));
    
        switch (temp) // ...welche dann aber wiederum umständlich einer Anweisung zugeordnet wird
        {
            case -1: break;
    
            case 0: exit_flag=1; break;
    
            case 1: cout << GLOBAL.getstrn(1) << "\n\n"; break;
    
            ...
        }
    }
    

    Kann man sowas nicht etwas eleganter und funktioneller realisieren?



  • Du könntest eine std::map mit Strings für deine Befehle und Funktionszeiger machen. Jede Funktion kriegt dann ungefähr wie main die Argumente übergeben? Nur so ne Idee... 😉



  • Ich hab das mit dem MVC-Modell implementiert. Dazu hab ich ein Singleton Console und eine Basisklasse IConsoleCommand, die so aussieht:

    /// Interface für Konsolenkommandos
        class IConsoleCommand
        {
        public:
    	/// Gibt den Namen des Kommandos zurück
    	virtual std::vector<std::string> getName () const = 0;
    
    	/// Führt die Aktion aus
    	virtual std::string action () = 0;
    
    	/// Zeigt eine Beschreibung des Kommandos an, auch über --help
    	virtual std::string description () const = 0;
    
        };
    

    Console sieht so aus:

    class Console : public boost::noncopyable
        {
        public:
    	typedef IConsoleCommand* ConsoleCommand;
    	typedef std::multimap<std::string, ConsoleCommand> CommandMap;
    
    	static Console* instance ()
    	{
    	    return &msInstance;
    	}
    
    	void registerCommand (ConsoleCommand);
    
    	const CommandMap& getCommands ()
    	{
    	    return mCommands;
    	}
    
    	/// Startet die Konsole
    	void run ();
    
        protected:
    	/// Konstruktor
    	Console () { mRunning = false; }
    	/// virtueller Destruktor
    	virtual ~Console () { }
        private:
    	/// Liste der bereits registrierten Konsolen-Kommandos
    	CommandMap mCommands;
    	/// Läuft die Konsole ? 
    	bool mRunning;
    
    	/// Singleton Instanz
    	static Console msInstance;
        };
    

    Und Console.cpp sieht so aus:

    #include "Console.hpp"
    #include <iostream>
    #include <string>
    #include <vector>
    #include <utility>
    
    using namespace Universum::UserInterface::Console;
    using namespace std;
    
    Console Console::msInstance;
    
    void Console::registerCommand (ConsoleCommand newCommand)
    {
        vector<std::string> names = newCommand->getName ();
        for (vector<std::string>::iterator i = names.begin ();
    	    i != names.end ();
    	    ++i)
    	mCommands.insert (make_pair ((*i), newCommand));
    }
    
    void Console::run ()
    {
        std::string cmd = "";
        CommandMap::iterator i;
        ConsoleCommand cmd_c;
    
        mRunning = true;
    
        while (mRunning)
        {
    	cout << "$ ";
    	getline (cin, cmd);
    
    	if (cmd == "exit")
    	    mRunning = false;
    	else if (!parse (cmd))
    	    cout << "Kommando " << cmd << " nicht gefunden" << endl;
        }
    }
    

    Jetzt müsstest du nur noch Console::parse () und ein Kommando implementieren z.B.:

    class CMDCount : public IConsoleCommand
        {
        public:
    	CMDCount ()
    	{
    	    mCounter = 0;
    	}
    	virtual std::vector<std::string> getName () const
    	{
    	    return myNames("count cnt");
    	}
    	virtual std::string action ()
    	{
    	    std::string str;
    	    std::ostringstream os (str);
    	    os << "Anzahl der Aufrufe:\t" << ++mCounter << std::ends;
    	    return os.str ();
    	}
    	virtual std::string description () const
    	{
    	    return "Zaehlt die Aufrufe seiner selbst";
    	}
        private:
    	unsigned int mCounter;
        };
    

    ... und das Kommando im Hauptprogramm registrieren:

    Console& c = *Console::instance ();
        c.registerCommand (new CMDCount);
    

    Ist bestimmt auch nicht der effizienteste Weg, aber nicht so C-style wie ein Switch.
    Für die parse Funktion würde ich Spirit benutzen, is aber recht kompliziert (wie ich finde).


Anmelden zum Antworten