From: g0dil Date: Tue, 13 May 2008 15:00:46 +0000 (+0000) Subject: Console: More simple argument parsing (argument iterator wrapper) X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=fa5eaa97c8593e3587c87f25adb14f7f91f31f37;p=senf.git Console: More simple argument parsing (argument iterator wrapper) git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@842 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Console/Mainpage.dox b/Console/Mainpage.dox index d094f24..c143bc3 100644 --- a/Console/Mainpage.dox +++ b/Console/Mainpage.dox @@ -288,28 +288,22 @@ \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 @@ -916,23 +910,23 @@ 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::parse( - senf::console::ParseCommandInfo::TokensRange( tokens.begin(), tokens.begin()+1 ), - out.x ) - senf::console::ArgumentTraits::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) } diff --git a/Console/Node.hh b/Console/Node.hh index e443205..0349831 100644 --- a/Console/Node.hh +++ b/Console/Node.hh @@ -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 diff --git a/Console/Parse.cci b/Console/Parse.cci index c9ff9ad..011ff9d 100644 --- a/Console/Parse.cci +++ b/Console/Parse.cci @@ -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() diff --git a/Console/Parse.hh b/Console/Parse.hh index 8c2ab32..892c522 100644 --- a/Console/Parse.hh +++ b/Console/Parse.hh @@ -196,6 +196,7 @@ #include #include #include +#include "../Utils/safe_bool.hh" //#include "Parse.mpp" ///////////////////////////////hh.p//////////////////////////////////////// @@ -285,9 +286,10 @@ namespace console { { typedef std::vector Tokens; typedef std::vector 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 + + { + 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 */ diff --git a/Console/Parse.test.cc b/Console/Parse.test.cc index 8171ce9..46ad986 100644 --- a/Console/Parse.test.cc +++ b/Console/Parse.test.cc @@ -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 index 0000000..d9d5a98 --- /dev/null +++ b/Console/Traits.ct @@ -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 +// +// 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 +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(tokens.begin()[0].value()); + } + catch (std::bad_cast & ex) { + throw SyntaxErrorException("parameter syntax error"); + } +} + +///////////////////////////////ct.e//////////////////////////////////////// +#undef prefix_ + + +// 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: diff --git a/Console/Traits.cti b/Console/Traits.cti index 6e777ef..4ffa5a7 100644 --- a/Console/Traits.cti +++ b/Console/Traits.cti @@ -60,19 +60,9 @@ parse(ParseCommandInfo::TokensRange const & tokens, Type & out) } template -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(tokens.begin()[0].value()); - } - catch (std::bad_cast & ex) { - throw SyntaxErrorException("parameter syntax error"); - } + ArgumentTraits::parse(tokens, out); } template diff --git a/Console/Traits.hh b/Console/Traits.hh index 4346eb0..69533b4 100644 --- a/Console/Traits.hh +++ b/Console/Traits.hh @@ -123,6 +123,16 @@ namespace console { template 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 + 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