Console: More simple argument parsing (argument iterator wrapper)
g0dil [Tue, 13 May 2008 15:00:46 +0000 (15:00 +0000)]
git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@842 270642c3-0616-0410-b53a-bc976706d245

Console/Mainpage.dox
Console/Node.hh
Console/Parse.cci
Console/Parse.hh
Console/Parse.test.cc
Console/Traits.ct [new file with mode: 0644]
Console/Traits.cti
Console/Traits.hh

index d094f24..c143bc3 100644 (file)
     \code
     void fun1(std::ostream & os, senf::console::ParseCommandInfo const & command)
     {
-        ParseCommandInfo::ArgumentsRange args (command.arguments());
-        ParseCommandInfo::ArgumentsRange::iterator arg (args.begin());
-
-        // Check that we are not missing our argument
-        if (arg == args.end())
-            raise senf::console::SyntaxErrorException("invalid number of arguments");
-
-        senf::console::ParseCommandInfo::TokensRange & arg1Tokens ( *(arg++) );
-
-        // Check that we don't have additional arguments
-        if (arg != args.end())
-            raise senf::console::SyntaxErrorException("invalid number of arguments");
-
-        // The argument must have exactly one token
-        if (arg1Tokens.size() != 1)
-            raise senf::console::SyntaxErrorException("argument syntax error");
-
-        // Retrieve the token value
-        std::string arg (argTokens[0].value());
-
-        // In this example, we just write the argument to the output stream
-        os << arg << std::endl;
+        // Here we declare variables for the arguments
+        std::string value;
+
+        {
+            // We parse the arguments using the CheckedArgumentIteratorWrapper. This wrapper
+            // will throw a SyntaxErrorException if we access a nonexistent argument or if we
+            // do not parse all arguments.
+            senf::console::CheckedArgumentIteratorWrapper args (command.arguments());
+
+            senf::console::ParseCommandInfo::TokensRange argTokens ( *(args++) );
+            if (arg1Tokens.size() != 1)
+                raise senf::console::SyntaxErrorException("argument syntax error");
+            value = arg1Tokens[0];
+        }
+
+        os << value << std::endl;
     }
     \endcode
     
     void senf_console_parse_argument(senf::console::ParseCommandInfo::TokensRange const & tokens,
                                      Coordinate & out)
     {
-        if (tokens.size() != 2)
-            throw SyntaxErrorException("parameter syntax error");
-        senf::console::ArgumentTraits<int>::parse(
-            senf::console::ParseCommandInfo::TokensRange( tokens.begin(), tokens.begin()+1 ),
-            out.x )
-        senf::console::ArgumentTraits<int>::parse(
-            senf::console::ParseCommandInfo::TokensRange( tokens.begin()+1, tokens.end() ),
-            out.y )
+        senf::console::CheckedArgumentIteratorWrapper arg (tokens);
+        senf::console::parse( *(arg++), out.x );
+        senf::console::parse( *(arg++), out.y );
     }
 
     void senf_console_format_value(Coordinate const & value, std::ostream & os)
     {
         os << '(' << value.x << ' ' << value.y << ')';
     }
-    \endcode
+    \endcode 
+
     The parser will accept an argument with two tokens which are each forwarded to the integer
-    parser. The formatter writes out the value as a parenthesized pair.
+    parser. The senf::console::CheckedArgumentIteratorWrapper ensures two things: That all input
+    tokens are parsed and no extra trailing tokens are left unparsed and it checks, that all
+    referenced tokens really exist.
+
+    The formatter writes out the value as a parenthesized pair.
 
     \code
     Coordinate fun5(Coordinate const & p) { return Coordinate(2*p.x, 2*p.y) }
index e443205..0349831 100644 (file)
@@ -520,23 +520,6 @@ namespace console {
     {};
 #endif
 
-    /**  \brief Syntax error parsing command arguments exception
-
-        All errors while parsing the arguments of a command must be signaled by throwing an instance
-        of SyntaxErrorException. This is important, so command overloading works.
-     */
-    struct SyntaxErrorException : public std::exception
-    {
-        explicit SyntaxErrorException(std::string const & msg = "");
-        virtual ~SyntaxErrorException() throw();
-
-        virtual char const * what() const throw();
-        std::string const & message() const;
-
-    private:
-        std::string message_;
-    };
-
     /** \brief Config/console tree command node
 
         The CommandNode is the base-class for the tree leaf nodes. Concrete command node
index c9ff9ad..011ff9d 100644 (file)
@@ -150,6 +150,86 @@ prefix_ void senf::console::ParseCommandInfo::ArgumentIterator::increment()
 }
 
 ///////////////////////////////////////////////////////////////////////////
+
+prefix_ senf::console::CheckedArgumentIteratorWrapper::
+CheckedArgumentIteratorWrapper(ParseCommandInfo::ArgumentsRange const & range,
+                               std::string const & msg)
+    : i_ (range.begin()), e_ (range.end()), msg_ (msg)
+{}
+
+prefix_ senf::console::CheckedArgumentIteratorWrapper::
+CheckedArgumentIteratorWrapper(ParseCommandInfo::TokensRange const & range,
+                               std::string const & msg)
+    : i_ (range.begin()), e_ (range.end()), msg_ (msg)
+{}
+
+prefix_ senf::console::CheckedArgumentIteratorWrapper::~CheckedArgumentIteratorWrapper()
+{
+    if (i_ != e_ && ! std::uncaught_exception())
+        throw SyntaxErrorException(msg_);
+}
+
+prefix_ senf::console::CheckedArgumentIteratorWrapper::operator ParseCommandInfo::ArgumentIterator()
+{
+    return i_;
+}
+
+prefix_ bool senf::console::CheckedArgumentIteratorWrapper::boolean_test()
+    const
+{
+    return i_ != e_;
+}
+
+prefix_ bool senf::console::CheckedArgumentIteratorWrapper::done()
+    const
+{
+    return i_ == e_;
+}
+
+prefix_ void senf::console::CheckedArgumentIteratorWrapper::clear()
+{
+    i_ = e_;
+}
+
+prefix_ senf::console::CheckedArgumentIteratorWrapper::reference
+senf::console::CheckedArgumentIteratorWrapper::dereference()
+    const
+{
+    if (i_ == e_)
+        throw SyntaxErrorException(msg_);
+    return *i_;
+}
+
+prefix_ void senf::console::CheckedArgumentIteratorWrapper::increment()
+{
+    if (i_ == e_)
+        throw SyntaxErrorException(msg_);
+    ++ i_;
+}
+
+prefix_ bool senf::console::CheckedArgumentIteratorWrapper::
+operator==(ParseCommandInfo::ArgumentIterator const & other)
+    const
+{
+    return i_ == other;
+}
+
+prefix_ bool senf::console::CheckedArgumentIteratorWrapper::
+operator!=(ParseCommandInfo::ArgumentIterator const & other)
+    const
+{
+    return i_ != other;
+}
+
+prefix_ senf::console::ParseCommandInfo::ArgumentIterator
+senf::console::CheckedArgumentIteratorWrapper::operator++(int)
+{
+    ParseCommandInfo::ArgumentIterator i (i_);
+    increment();
+    return i;
+}
+
+///////////////////////////////////////////////////////////////////////////
 // senf::console::SingleCommandParser
 
 prefix_ senf::console::CommandParser::Impl & senf::console::CommandParser::impl()
index 8c2ab32..892c522 100644 (file)
 #include <boost/range/iterator_range.hpp>
 #include <boost/iterator/iterator_facade.hpp>
 #include <boost/function.hpp>
+#include "../Utils/safe_bool.hh"
 
 //#include "Parse.mpp"
 ///////////////////////////////hh.p////////////////////////////////////////
@@ -285,9 +286,10 @@ namespace console {
     {
         typedef std::vector<ArgumentToken> Tokens;
         typedef std::vector<std::string> CommandPath;
-        class ArgumentIterator; 
 
     public:
+        class ArgumentIterator; 
+
         typedef CommandPath::const_iterator path_iterator;
         typedef Tokens::const_iterator token_iterator;
         typedef ArgumentIterator argument_iterator;
@@ -338,17 +340,27 @@ namespace console {
         friend class detail::ParserAccess;
     };
 
+    /** \brief Iterator parsing argument groups
+
+        This special iterator parses a token range returned by the parser into argument ranges. An
+        argument range is either a single token or it is a range of tokens enclosed in matching
+        parenthesis. The ParseCommandInfo::arguments() uses this iterator type. To recursively parse
+        complex arguments, you can however use this iterator to divide a multi-token argument into
+        further argument groups (e.g. to parse a list or vector of items).
+
+        This iterator is a bidirectional iterator \e not a random access iterator.
+     */
     class ParseCommandInfo::ArgumentIterator
         : public boost::iterator_facade< ParseCommandInfo::ArgumentIterator, 
                                          ParseCommandInfo::TokensRange,
                                          boost::bidirectional_traversal_tag,
                                          ParseCommandInfo::TokensRange >
     {
+    public:
         ArgumentIterator();
+        explicit ArgumentIterator(ParseCommandInfo::TokensRange::iterator i);
 
     private:
-        ArgumentIterator(ParseCommandInfo::TokensRange::iterator i);
-
         reference dereference() const;
         bool equal(ArgumentIterator const & other) const;
         void increment();
@@ -363,6 +375,128 @@ namespace console {
         friend class ParseCommandInfo;
     };
 
+    /**  \brief Syntax error parsing command arguments exception
+
+        All errors while parsing the arguments of a command must be signaled by throwing an instance
+        of SyntaxErrorException. This is important, so command overloading works.
+     */
+    struct SyntaxErrorException : public std::exception
+    {
+        explicit SyntaxErrorException(std::string const & msg = "");
+        virtual ~SyntaxErrorException() throw();
+
+        virtual char const * what() const throw();
+        std::string const & message() const;
+
+    private:
+        std::string message_;
+    };
+
+    /** \brief Wrapper checking argument iterator access for validity
+        
+        CheckedArgumentIteratorWrapper is a wrapper around a range of arguments parsed using the
+        ParseCommandInfo::ArgumentIterator. It is used to parse arguments either in a command
+        (registered with manual argument parsing) or when defining a custom parser.
+        \code
+        void fn(std::ostream & out, senf::console::ParseCommandInfo command)
+        {
+            std:;string arg1;
+            unsigned arg2 (0);
+            
+            {
+                senf::console::CheckedArgumentIteratorWrapper arg (command.arguments());
+                senf::console::parse( *(arg++), arg1 );
+                senf::console::parse( *(arg++), arg2 );
+            }
+
+            // ...
+        }
+        \endcode
+
+        To use the wrapper, you must ensure that:
+        \li You increment the iterator \e past all arguments you parse. The iterator must point to
+            the end of the range when parsing is complete.
+        \li The iterator wrapper is destroyed after parsing but before executing the command itself
+            begins. 
+
+        Accessing a non-existent argument or failing to parse all arguments will raise a
+        senf::console::SyntaxErrorException.
+
+        \see \link console_arg_custom Example customer parser \endlink
+      */
+    class CheckedArgumentIteratorWrapper
+        : boost::noncopyable,
+          public boost::iterator_facade< CheckedArgumentIteratorWrapper,
+                                         ParseCommandInfo::TokensRange,
+                                         boost::forward_traversal_tag,
+                                         ParseCommandInfo::TokensRange >,
+          public senf::safe_bool<CheckedArgumentIteratorWrapper>
+
+    {
+        typedef boost::iterator_facade< CheckedArgumentIteratorWrapper,
+                                        ParseCommandInfo::TokensRange,
+                                        boost::forward_traversal_tag,
+                                        ParseCommandInfo::TokensRange > IteratorFacade;
+
+    public:
+        explicit CheckedArgumentIteratorWrapper(
+            ParseCommandInfo::ArgumentsRange const & range,
+            std::string const & msg = "invalid number of arguments");
+                                        ///< Make wrapper from ArgumentsRange
+                                        /**< This constructs a wrapper from a
+                                             ParseCommandInfo::ArgumentsRange. 
+                                             \param[in] range Range of arguments to parse
+                                             \param[in] msg Error message */
+        explicit CheckedArgumentIteratorWrapper(
+            ParseCommandInfo::TokensRange const & range,
+            std::string const & msg = "invalid number of arguments");
+                                        ///< Make wrapper from TokensRange
+                                        /**< This constructs a wrapper from a
+                                             ParseCommandInfo::TokensRange. The TokensRange is first
+                                             converted into an ParseCommandInfo::ArgumentsRange
+                                             which is then wrapped.
+                                             \param[in] range Range of tokens to  parse
+                                             \param[in] msg Error message */
+
+        ~CheckedArgumentIteratorWrapper(); ///< Check, if all arguments are parsed
+                                        /**< The destructor validates, that all arguments are parsed
+                                             correctly when leaving the scope, in which the wrapper
+                                             is instantiated normally (not by an exception).
+
+                                             \warning This destructor will throw a
+                                             SyntaxErrorException, if not all arguments are parsed
+                                             and when no other exception is in progress. */
+
+        operator ParseCommandInfo::ArgumentIterator(); 
+                                        ///< Use wrapper as ParseCommandInfo::ArgumentIterator
+
+        bool boolean_test() const;      ///< \c true, if more arguments are available
+        bool done() const;              ///< \c true, if all arguments are parsed
+
+        void clear();                   ///< Set range empty
+                                        /**< This call will point the current iterator to the end of
+                                             the tokens range.
+                                             \post done() == \c true; */
+
+        bool operator==(ParseCommandInfo::ArgumentIterator const & other) const;
+                                        ///< Compare wrapper against ArgumentIterator
+        bool operator!=(ParseCommandInfo::ArgumentIterator const & other) const;
+                                        ///< Compare wrapper against ArgumentIterator
+        
+        using IteratorFacade::operator++;
+        ParseCommandInfo::ArgumentIterator operator++(int);
+        
+    private:
+        reference dereference() const;
+        void increment();
+
+        ParseCommandInfo::ArgumentIterator i_;
+        ParseCommandInfo::ArgumentIterator e_;
+        std::string msg_;
+
+        friend class boost::iterator_core_access;
+    };
+
     /**< \brief Output ParseCommandInfo instance
          \related ParseCommandInfo
       */
index 8171ce9..46ad986 100644 (file)
@@ -212,6 +212,41 @@ BOOST_AUTO_UNIT_TEST(commandParser)
     BOOST_CHECK( args == info.arguments().end() );
 }
 
+namespace {
+    void parseArgs(senf::console::ParseCommandInfo::ArgumentsRange const & args)
+    {
+        senf::console::CheckedArgumentIteratorWrapper arg (args);
+        senf::console::ParseCommandInfo::TokensRange arg1 (*(arg++));
+        senf::console::ParseCommandInfo::TokensRange arg2 (*(arg++));
+    }
+}
+
+BOOST_AUTO_UNIT_TEST(checkedArgumentIterator)
+{
+    senf::console::CommandParser parser;
+
+    BOOST_CHECK( parser.parse("foo a", &setInfo) );
+    BOOST_CHECK_THROW( parseArgs(info.arguments()), senf::console::SyntaxErrorException );
+
+    BOOST_CHECK( parser.parse("foo a b", &setInfo) );
+    BOOST_CHECK_NO_THROW( parseArgs(info.arguments()) );
+
+    BOOST_CHECK( parser.parse("foo a b c", &setInfo) );
+    BOOST_CHECK_THROW( parseArgs(info.arguments()), senf::console::SyntaxErrorException );
+    
+    senf::console::CheckedArgumentIteratorWrapper arg (info.arguments());
+    BOOST_CHECK( arg == info.arguments().begin() );
+    BOOST_CHECK( arg != info.arguments().end() );
+    BOOST_CHECK( arg );
+    ++ arg;
+    BOOST_CHECK( arg );
+    arg.clear();
+    BOOST_CHECK( arg.done() );
+
+    senf::console::ParseCommandInfo::ArgumentIterator i (arg);
+    BOOST_CHECK( i == info.arguments().end() );
+}
+
 ///////////////////////////////cc.e////////////////////////////////////////
 #undef prefix_
 
diff --git a/Console/Traits.ct b/Console/Traits.ct
new file mode 100644 (file)
index 0000000..d9d5a98
--- /dev/null
@@ -0,0 +1,61 @@
+// $Id$
+//
+// Copyright (C) 2008 
+// Fraunhofer Institute for Open Communication Systems (FOKUS)
+// Competence Center NETwork research (NET), St. Augustin, GERMANY
+//     Stefan Bund <g0dil@berlios.de>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the
+// Free Software Foundation, Inc.,
+// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+/** \file
+    \brief Traits non-inline template implementation  */
+
+#include "Traits.ih"
+
+// Custom includes
+
+#define prefix_
+///////////////////////////////ct.p////////////////////////////////////////
+
+template <class Type>
+prefix_ void
+senf::console::senf_console_parse_argument(ParseCommandInfo::TokensRange const & tokens,
+                                           Type & out)
+{
+    if (tokens.size() != 1)
+        throw SyntaxErrorException("parameter syntax error");
+
+    try {
+        out = boost::lexical_cast<Type>(tokens.begin()[0].value());
+    }
+    catch (std::bad_cast & ex) {
+        throw SyntaxErrorException("parameter syntax error");
+    }
+}
+
+///////////////////////////////ct.e////////////////////////////////////////
+#undef prefix_
+
+\f
+// Local Variables:
+// mode: c++
+// fill-column: 100
+// comment-column: 40
+// c-file-style: "senf"
+// indent-tabs-mode: nil
+// ispell-local-dictionary: "american"
+// compile-command: "scons -u test"
+// End:
index 6e777ef..4ffa5a7 100644 (file)
@@ -60,19 +60,9 @@ parse(ParseCommandInfo::TokensRange const & tokens, Type & out)
 }
 
 template <class Type>
-prefix_ void
-senf::console::senf_console_parse_argument(ParseCommandInfo::TokensRange const & tokens,
-                                           Type & out)
+prefix_ void senf::console::parse(ParseCommandInfo::TokensRange const & tokens, Type & out)
 {
-    if (tokens.size() != 1)
-        throw SyntaxErrorException("parameter syntax error");
-
-    try {
-        out = boost::lexical_cast<Type>(tokens.begin()[0].value());
-    }
-    catch (std::bad_cast & ex) {
-        throw SyntaxErrorException("parameter syntax error");
-    }
+    ArgumentTraits<Type>::parse(tokens, out);
 }
 
 template <class Type>
index 4346eb0..69533b4 100644 (file)
@@ -123,6 +123,16 @@ namespace console {
     template <class Type>
     void senf_console_parse_argument(ParseCommandInfo::TokensRange const & tokens, Type & out);
 
+    /** \brief Parse token range
+
+        This helper will invoke the correct ArgumentTraits::parse function to parse the input tokens
+        into the passed in variable.
+
+        \see ArgumentTraits
+     */
+    template <class Type>
+    void parse(ParseCommandInfo::TokensRange const & tokens, Type & out);
+
     /** \brief Register enum type for argument parsing
 
         Enum types need to be registered explicitly to support parsing. 
@@ -160,7 +170,7 @@ namespace console {
 
 ///////////////////////////////hh.e////////////////////////////////////////
 //#include "Traits.cci"
-//#include "Traits.ct"
+#include "Traits.ct"
 #include "Traits.cti"
 #endif