From: g0dil Date: Thu, 29 May 2008 23:13:04 +0000 (+0000) Subject: Scheduler: Implement Timer helper X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=feeec0e9cd78825120bd52f9ef4e115d383bf6a8;p=senf.git Scheduler: Implement Timer helper Console: Add incremental parsing support Console: Implement non-interactive network console git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@860 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Console/Parse.cc b/Console/Parse.cc index bc4fd46..57a5532 100644 --- a/Console/Parse.cc +++ b/Console/Parse.cc @@ -45,12 +45,11 @@ namespace detail { struct ParseDispatcher { ParseCommandInfo * info_; - CommandParser::Callback cb_; struct BindInfo { - BindInfo( ParseDispatcher & d, ParseCommandInfo & info, CommandParser::Callback cb) - : dispatcher (d) { dispatcher.info_ = &info; dispatcher.cb_ = cb; } - ~BindInfo() { dispatcher.info_ = 0; dispatcher.cb_ = 0; } + BindInfo( ParseDispatcher & d, ParseCommandInfo & info) + : dispatcher (d) { dispatcher.info_ = &info; } + ~BindInfo() { dispatcher.info_ = 0; } ParseDispatcher & dispatcher; }; @@ -60,7 +59,7 @@ namespace detail { info_->command(command); } void endCommand() - { cb_(*info_); } + { } void pushToken(Token const & token) { info_->addToken(token); } @@ -68,36 +67,30 @@ namespace detail { void builtin_cd(std::vector & path) { info_->clear(); info_->builtin(ParseCommandInfo::BuiltinCD); - setBuiltinPathArg(path); - cb_(*info_); } + setBuiltinPathArg(path); } void builtin_ls(std::vector & path) { info_->clear(); info_->builtin(ParseCommandInfo::BuiltinLS); - setBuiltinPathArg(path); - cb_(*info_); } + setBuiltinPathArg(path); } void pushDirectory(std::vector & path) { info_->clear(); info_->builtin(ParseCommandInfo::BuiltinPUSHD); - setBuiltinPathArg(path); - cb_(*info_); } + setBuiltinPathArg(path); } void popDirectory() { info_->clear(); - info_->builtin(ParseCommandInfo::BuiltinPOPD); - cb_(*info_); } + info_->builtin(ParseCommandInfo::BuiltinPOPD); } void builtin_exit() { info_->clear(); - info_->builtin(ParseCommandInfo::BuiltinEXIT); - cb_(*info_); } + info_->builtin(ParseCommandInfo::BuiltinEXIT); } void builtin_help(std::vector & path) { info_->clear(); info_->builtin(ParseCommandInfo::BuiltinHELP); - setBuiltinPathArg(path); - cb_(*info_); } + setBuiltinPathArg(path); } void setBuiltinPathArg(std::vector & path) { @@ -132,14 +125,16 @@ prefix_ std::ostream & senf::console::operator<<(std::ostream & os, Token const "Word" }; // The real table is: // static const int bitPosition[32] = { - // 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, - // 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; - // However, we have replaced all values > sizeof(tokenTypeName) with 0 + // 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + // 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; + // However, we have replaced all values >= sizeof(tokenTypeName) with 0 + // and have added 1 to all the remaining values static const int bitPosition[32] = { - 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 4, 8, - 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 6, 0, 5, 10, 9 }; + 1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 5, 9, + 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 7, 0, 6, 0, 10 }; + // We need to check token.type() against 0 explicitly since 0 and 1 will both be mapped to 0 os << tokenTypeName[ token.type() - ? bitPosition[((token.type() & -token.type()) * 0x077CB531UL) >> 27]+1 + ? bitPosition[((token.type() & -token.type()) * 0x077CB531UL) >> 27] : 0 ] << "('" << token.value() @@ -165,7 +160,7 @@ prefix_ std::ostream & senf::console::operator<<(std::ostream & stream, } } else { - char const * builtins[] = { "", "cd", "ls", "pushd", "popd", "exit", "help" }; + char const * builtins[] = { 0, "cd", "ls", "pushd", "popd", "exit", "help" }; stream << "builtin-" << builtins[info.builtin()]; } @@ -253,40 +248,79 @@ prefix_ senf::console::CommandParser::CommandParser() prefix_ senf::console::CommandParser::~CommandParser() {} -prefix_ bool senf::console::CommandParser::parse(std::string command, Callback cb) +// This template member is placed here, since it is ONLY called from the implementation. Otherwise, +// we would need to expose the Impl member to the public, which we don't want to do. + +template +prefix_ Iterator senf::console::CommandParser::parseLoop(Iterator b, Iterator e, Callback cb) { ParseCommandInfo info; - detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info, cb); - return boost::spirit::parse( command.begin(), command.end(), - impl().grammar.use_parser(), - impl().grammar.use_parser() - ).full; + detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info); + boost::spirit::parse_info result; + + for(;;) { + result = boost::spirit::parse( + b, e, * impl().grammar.use_parser()); + b = result.stop; + if (b == e) + return e; + info.clear(); + result = boost::spirit::parse(b, e, + impl().grammar.use_parser(), + impl().grammar.use_parser()); + if (! result.hit) + return b; + if (! info.empty()) + cb(info); + b = result.stop; + } } -prefix_ bool senf::console::CommandParser::parseFile(std::string filename, Callback cb) +prefix_ bool senf::console::CommandParser::parse(std::string const & command, Callback cb) +{ + return parseLoop(command.begin(), command.end(), cb) == command.end(); +} + +prefix_ bool senf::console::CommandParser::parseFile(std::string const & filename, Callback cb) { - ParseCommandInfo info; - detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info, cb); boost::spirit::file_iterator<> i (filename); if (!i) throw SystemException(ENOENT SENF_EXC_DEBUGINFO); boost::spirit::file_iterator<> const i_end (i.make_end()); - return boost::spirit::parse( i, i_end, - impl().grammar.use_parser(), - impl().grammar.use_parser() - ).full; + return parseLoop(i, i_end, cb) == i_end; } -prefix_ bool senf::console::CommandParser::parseArguments(std::string arguments, +prefix_ bool senf::console::CommandParser::parseArguments(std::string const & arguments, ParseCommandInfo & info) { - detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info, 0); + detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info); return boost::spirit::parse( arguments.begin(), arguments.end(), impl().grammar.use_parser(), impl().grammar.use_parser() ).full; } +struct senf::console::CommandParser::SetIncremental +{ + SetIncremental(CommandParser & parser) : parser_ (parser) { + parser_.impl().grammar.incremental = true; + } + + ~SetIncremental() { + parser_.impl().grammar.incremental = false; + } + + CommandParser & parser_; +}; + +prefix_ std::string::size_type +senf::console::CommandParser::parseIncremental(std::string const & commands, Callback cb) +{ + SetIncremental si (*this); + return std::distance( commands.begin(), + parseLoop(commands.begin(), commands.end(), cb) ); +} + /////////////////////////////////////////////////////////////////////////// // senf::console::SyntaxErrorException diff --git a/Console/Parse.cci b/Console/Parse.cci index fcce862..322fc44 100644 --- a/Console/Parse.cci +++ b/Console/Parse.cci @@ -170,6 +170,11 @@ prefix_ void senf::console::ParseCommandInfo::clear() tokens_.clear(); } +prefix_ bool senf::console::ParseCommandInfo::empty() +{ + return builtin_ == NoBuiltin && commandPath_.empty(); +} + prefix_ void senf::console::ParseCommandInfo::builtin(BuiltinCommand builtin) { builtin_ = builtin; diff --git a/Console/Parse.hh b/Console/Parse.hh index c246ce1..5653566 100644 --- a/Console/Parse.hh +++ b/Console/Parse.hh @@ -377,6 +377,7 @@ namespace console { single range not divided into separate arguments. */ void clear(); ///< Clear all data members + bool empty(); ///< \c true, if the data is empty void builtin(BuiltinCommand builtin); ///< Assign builtin command void command(std::vector & commandPath); ///< Assign non-builtin command @@ -595,24 +596,41 @@ namespace console { ///@} /////////////////////////////////////////////////////////////////////////// - bool parse(std::string command, Callback cb); ///< Parse string - bool parseFile(std::string filename, Callback cb); ///< Parse file + bool parse(std::string const & command, Callback cb); ///< Parse string + bool parseFile(std::string const & filename, Callback cb); ///< Parse file /**< \throws SystemException if the file cannot be read. */ - bool parseArguments(std::string arguments, ParseCommandInfo & info); + bool parseArguments(std::string const & arguments, ParseCommandInfo & info); ///< Parse \a argumtns /**< parseArguments() parses the string \a arguments which contains arbitrary command arguments (without the name of the command). The argument tokens are written into \a info. */ + std::string::size_type parseIncremental(std::string const & commands, Callback cb); + ///< Incremental parse + /**< An incremental parse will parse all complete statements + in \a commands. parseIncremental() will return the + number of characters successfully parsed from \a + commands. + + \note The incremental parser \e requires all statements + to be terminated explicitly. This means, that the + last ';' is \e not optional in this case. */ + private: struct Impl; + struct SetIncremental; + + template + Iterator parseLoop(Iterator b, Iterator e, Callback cb); Impl & impl(); boost::scoped_ptr impl_; + + friend class SetIncremental; }; }} diff --git a/Console/Parse.ih b/Console/Parse.ih index ae9a193..56628c9 100644 --- a/Console/Parse.ih +++ b/Console/Parse.ih @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include #include @@ -99,6 +101,11 @@ namespace detail { Context & context; /////////////////////////////////////////////////////////////////////////// + // Configuration + + bool incremental; + + /////////////////////////////////////////////////////////////////////////// // Dispatching semantic actions ParseDispatcher & dispatcher; @@ -169,7 +176,7 @@ namespace detail { /////////////////////////////////////////////////////////////////////////// CommandGrammar(ParseDispatcher & d, Context & c) - : context(c), dispatcher(d) {} + : context(c), incremental(false), dispatcher(d) {} template struct definition @@ -179,7 +186,8 @@ namespace detail { { boost::spirit::rule command, path, argument, word, string, hexstring, token, punctuation, hexbyte, balanced_tokens, simple_argument, complex_argument, builtin, - skip, commands, block, statement, relpath, abspath, arguments; + skip, statement, relpath, abspath, arguments, group_start, group_close, + statement_end; boost::spirit::chset<> special_p, punctuation_p, space_p, invalid_p, word_p; boost::spirit::distinct_parser<> keyword_p; @@ -209,6 +217,7 @@ namespace detail { { using namespace boost::spirit; + using namespace phoenix; typedef ParseDispatcher PD; typedef Token AT; @@ -218,11 +227,13 @@ namespace detail { // Syntax summary: // This is EBNF with some minor tweaks to accommodate C++ syntax // - // * and + like EBNF but they precede their argument - // >> is followed by - // ! optional - // a % b match any number of a's separated by b - // a - b match a but not b + // * a any number of a's + // + a at least one a + // ! a an optional a + // a >> b a followed by b + // a | b a or b + // a % b any number of a's separated by b's + // a - b a but not b // // Beside this, we use some special parsers (ch_p, eps_p, confix_p, lex_escape_ch_p, // keyword_p, comment_p) and directives (lexeme_d), however, the parser should be @@ -248,13 +259,10 @@ namespace detail { // // More info is in the Boost.Spirit documentation - commands - = * command - ; - command - = builtin >> (ch_p(';') | end_p) - | path >> ( block | statement ) + = builtin >> statement_end + | path >> ( group_start | statement ) + | group_close | ch_p(';') // Ignore empty commands ; @@ -275,19 +283,20 @@ namespace detail { boost::ref(self.context.path)) ] ; - block + group_start = ch_p('{') [ self.dispatch(&PD::pushDirectory, boost::ref(self.context.path)) ] - >> * command - >> ch_p('}') [ self.dispatch(&PD::popDirectory) ] + ; + + group_close + = ch_p('}') [ self.dispatch(&PD::popDirectory) ] ; statement - = eps_p [ self.dispatch(&PD::beginCommand, + = eps_p [ self.dispatch(&PD::beginCommand, boost::ref(self.context.path)) ] >> arguments - >> (ch_p(';') | end_p) - >> eps_p [ self.dispatch(&PD::endCommand) ] + >> statement_end [ self.dispatch(&PD::endCommand) ] ; arguments @@ -378,8 +387,7 @@ namespace detail { word // Returns value in context.token = lexeme_d [ - eps_p - >> (+ word_p) [ assign_a(self.context.str) ] + (+ word_p) [ assign_a(self.context.str) ] ] >> eps_p [ self.set_token_a(AT::Word, self.context.str) ] ; @@ -389,6 +397,16 @@ namespace detail { [ append_a(self.context.str) ] ; + statement_end + = if_p(var(self.incremental)) [ + ch_p(';') + ] + .else_p [ + ch_p(';') + | end_p + ] + ; + skip = space_p | comment_p('#') ; @@ -396,7 +414,7 @@ namespace detail { /////////////////////////////////////////////////////////////////// start_parsers( - commands, // CommandParser + command, // CommandParser skip, // SkipParser arguments // ArgumentsParser ); diff --git a/Console/Parse.test.cc b/Console/Parse.test.cc index c58a6cc..c650745 100644 --- a/Console/Parse.test.cc +++ b/Console/Parse.test.cc @@ -82,60 +82,104 @@ BOOST_AUTO_UNIT_TEST(commandGrammar) typedef senf::console::detail::CommandGrammar Grammar; Grammar grammar (dispatcher, context); - char text[] = - "# Comment\n" - "doo / bii / doo arg" - " flab::blub" - " 123.434>a" - " (a,b;c (huhu/{haha}))" - " \"foo\\\"bar\" #\n" - " x\"01 02 # Inner comment\n" - " 0304\";" - "ls /foo/bar;" - "cd /foo/bar;" - "exit;" - "foo/bar/ { ls; }" - "help /foo/bar"; - - BOOST_CHECK( boost::spirit::parse( - text, - grammar.use_parser(), - grammar.use_parser() ) . full ); - BOOST_CHECK_EQUAL( ss.str(), - "beginCommand( Word('doo')/Word('bii')/Word('doo') )\n" - "pushToken( Word('arg') )\n" - "pushToken( Word('flab::blub') )\n" - "pushToken( Word('123.434>a') )\n" - "pushToken( ArgumentGroupOpen('(') )\n" - "pushToken( Word('a') )\n" - "pushToken( OtherPunctuation(',') )\n" - "pushToken( Word('b') )\n" - "pushToken( CommandTerminator(';') )\n" - "pushToken( Word('c') )\n" - "pushToken( ArgumentGroupOpen('(') )\n" - "pushToken( Word('huhu') )\n" - "pushToken( PathSeparator('/') )\n" - "pushToken( DirectoryGroupOpen('{') )\n" - "pushToken( Word('haha') )\n" - "pushToken( DirectoryGroupClose('}') )\n" - "pushToken( ArgumentGroupClose(')') )\n" - "pushToken( ArgumentGroupClose(')') )\n" - "pushToken( BasicString('foo\"bar') )\n" - "pushToken( HexString('\x01\x02\x03\x04') )\n" - "endCommand()\n" - "builtin_ls( None('')/Word('foo')/Word('bar') )\n" - "builtin_cd( None('')/Word('foo')/Word('bar') )\n" - "builtin_exit()\n" - "pushDirectory( Word('foo')/Word('bar')/None('') )\n" - "builtin_ls( )\n" - "popDirectory()\n" - "builtin_help( None('')/Word('foo')/Word('bar') )\n" ); + { + static char text[] = + "# Comment\n" + "doo / bii / doo arg" + " flab::blub" + " 123.434>a" + " (a,b;c (huhu/{haha}))" + " \"foo\\\"bar\" #\n" + " x\"01 02 # Inner comment\n" + " 0304\";"; + + BOOST_CHECK( boost::spirit::parse( + text, + grammar.use_parser(), + grammar.use_parser() ) . full ); + BOOST_CHECK_EQUAL( ss.str(), + "beginCommand( Word('doo')/Word('bii')/Word('doo') )\n" + "pushToken( Word('arg') )\n" + "pushToken( Word('flab::blub') )\n" + "pushToken( Word('123.434>a') )\n" + "pushToken( ArgumentGroupOpen('(') )\n" + "pushToken( Word('a') )\n" + "pushToken( OtherPunctuation(',') )\n" + "pushToken( Word('b') )\n" + "pushToken( CommandTerminator(';') )\n" + "pushToken( Word('c') )\n" + "pushToken( ArgumentGroupOpen('(') )\n" + "pushToken( Word('huhu') )\n" + "pushToken( PathSeparator('/') )\n" + "pushToken( DirectoryGroupOpen('{') )\n" + "pushToken( Word('haha') )\n" + "pushToken( DirectoryGroupClose('}') )\n" + "pushToken( ArgumentGroupClose(')') )\n" + "pushToken( ArgumentGroupClose(')') )\n" + "pushToken( BasicString('foo\"bar') )\n" + "pushToken( HexString('\x01\x02\x03\x04') )\n" + "endCommand()\n" ); + } + + { + ss.str(""); + BOOST_CHECK( boost::spirit::parse( + "ls /foo/bar;", + grammar.use_parser(), + grammar.use_parser() ) . full ); + BOOST_CHECK_EQUAL( ss.str(), "builtin_ls( None('')/Word('foo')/Word('bar') )\n" ); + } + + { + ss.str(""); + BOOST_CHECK( boost::spirit::parse( + "cd /foo/bar;", + grammar.use_parser(), + grammar.use_parser() ) . full ); + BOOST_CHECK_EQUAL( ss.str(), "builtin_cd( None('')/Word('foo')/Word('bar') )\n" ); + } + + { + ss.str(""); + BOOST_CHECK( boost::spirit::parse( + "exit;", + grammar.use_parser(), + grammar.use_parser() ) . full ); + BOOST_CHECK_EQUAL( ss.str(), "builtin_exit()\n" ); + } + + { + ss.str(""); + BOOST_CHECK( boost::spirit::parse( + "foo/bar/ {", + grammar.use_parser(), + grammar.use_parser() ) . full ); + BOOST_CHECK_EQUAL( ss.str(), "pushDirectory( Word('foo')/Word('bar')/None('') )\n" ); + } + + { + ss.str(""); + BOOST_CHECK( boost::spirit::parse( + "}", + grammar.use_parser(), + grammar.use_parser() ) . full ); + BOOST_CHECK_EQUAL( ss.str(), "popDirectory()\n" ); + } + + { + ss.str(""); + BOOST_CHECK( boost::spirit::parse( + "help /foo/bar", + grammar.use_parser(), + grammar.use_parser() ) . full ); + BOOST_CHECK_EQUAL( ss.str(), "builtin_help( None('')/Word('foo')/Word('bar') )\n" ); + } } namespace { - senf::console::ParseCommandInfo info; + std::vector commands; void setInfo(senf::console::ParseCommandInfo const & i) - { info = i; } + { commands.push_back(i); } } BOOST_AUTO_UNIT_TEST(commandParser) @@ -150,60 +194,68 @@ BOOST_AUTO_UNIT_TEST(commandParser) " (a,b,c (huhu))" " \"foo\\\"bar\" #\n" " x\"01 02 # Inner comment\n" - " 0304\""; + " 0304\";" + "ls /foo/bar; "; BOOST_CHECK( parser.parse(text, &setInfo) ); + BOOST_CHECK_EQUAL( commands.size(), 2u ); - senf::console::Token path[] = { - senf::console::Token(senf::console::Token::Word, "doo"), - senf::console::Token(senf::console::Token::Word, "bii"), - senf::console::Token(senf::console::Token::Word, "doo") - }; + { + senf::console::ParseCommandInfo const & info (commands.front()); - BOOST_CHECK_EQUAL_COLLECTIONS( info.commandPath().begin(), info.commandPath().end(), - path, path + sizeof(path)/sizeof(path[0]) ); - BOOST_CHECK_EQUAL( info.tokens().size(), 15u ); - - char const * tokens[] = { "arg", - "flab::blub", - "123.434>a", - "(", "a", ",", "b", ",", "c", "(", "huhu", ")", ")", - "foo\"bar", - "\x01\x02\x03\x04" }; - - senf::console::ParseCommandInfo::argument_iterator args (info.arguments().begin()); - BOOST_REQUIRE( args != info.arguments().end() ); - BOOST_REQUIRE_EQUAL( args->size(), 1u ); - BOOST_CHECK_EQUAL( args->begin()->value(), tokens[0] ); - - ++ args; - BOOST_REQUIRE( args != info.arguments().end() ); - BOOST_REQUIRE_EQUAL( args->size(), 1u ); - BOOST_CHECK_EQUAL( args->begin()->value(), tokens[1] ); - - ++ args; - BOOST_REQUIRE( args != info.arguments().end() ); - BOOST_REQUIRE_EQUAL( args->size(), 1u ); - BOOST_CHECK_EQUAL( args->begin()->value(), tokens[2] ); - - ++ args; - BOOST_REQUIRE( args != info.arguments().end() ); - BOOST_REQUIRE_EQUAL( args->size(), 8u ); - for (unsigned i (0); i<8; ++i) - BOOST_CHECK_EQUAL( args->begin()[i].value(), tokens[4+i] ); - - ++ args; - BOOST_REQUIRE( args != info.arguments().end() ); - BOOST_REQUIRE_EQUAL( args->size(), 1u ); - BOOST_CHECK_EQUAL( args->begin()->value(), tokens[13] ); - - ++ args; - BOOST_REQUIRE( args != info.arguments().end() ); - BOOST_REQUIRE_EQUAL( args->size(), 1u ); - BOOST_CHECK_EQUAL( args->begin()->value(), tokens[14] ); - - ++ args; - BOOST_CHECK( args == info.arguments().end() ); + senf::console::Token path[] = { + senf::console::Token(senf::console::Token::Word, "doo"), + senf::console::Token(senf::console::Token::Word, "bii"), + senf::console::Token(senf::console::Token::Word, "doo") + }; + + BOOST_CHECK_EQUAL_COLLECTIONS( info.commandPath().begin(), info.commandPath().end(), + path, path + sizeof(path)/sizeof(path[0]) ); + BOOST_CHECK_EQUAL( info.tokens().size(), 15u ); + + char const * tokens[] = { "arg", + "flab::blub", + "123.434>a", + "(", "a", ",", "b", ",", "c", "(", "huhu", ")", ")", + "foo\"bar", + "\x01\x02\x03\x04" }; + + senf::console::ParseCommandInfo::argument_iterator args (info.arguments().begin()); + BOOST_REQUIRE( args != info.arguments().end() ); + BOOST_REQUIRE_EQUAL( args->size(), 1u ); + BOOST_CHECK_EQUAL( args->begin()->value(), tokens[0] ); + + ++ args; + BOOST_REQUIRE( args != info.arguments().end() ); + BOOST_REQUIRE_EQUAL( args->size(), 1u ); + BOOST_CHECK_EQUAL( args->begin()->value(), tokens[1] ); + + ++ args; + BOOST_REQUIRE( args != info.arguments().end() ); + BOOST_REQUIRE_EQUAL( args->size(), 1u ); + BOOST_CHECK_EQUAL( args->begin()->value(), tokens[2] ); + + ++ args; + BOOST_REQUIRE( args != info.arguments().end() ); + BOOST_REQUIRE_EQUAL( args->size(), 8u ); + for (unsigned i (0); i<8; ++i) + BOOST_CHECK_EQUAL( args->begin()[i].value(), tokens[4+i] ); + + ++ args; + BOOST_REQUIRE( args != info.arguments().end() ); + BOOST_REQUIRE_EQUAL( args->size(), 1u ); + BOOST_CHECK_EQUAL( args->begin()->value(), tokens[13] ); + + ++ args; + BOOST_REQUIRE( args != info.arguments().end() ); + BOOST_REQUIRE_EQUAL( args->size(), 1u ); + BOOST_CHECK_EQUAL( args->begin()->value(), tokens[14] ); + + ++ args; + BOOST_CHECK( args == info.arguments().end() ); + } + + commands.clear(); } namespace { @@ -220,17 +272,19 @@ 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_THROW( parseArgs(commands.back().arguments()), + senf::console::SyntaxErrorException ); BOOST_CHECK( parser.parse("foo a b", &setInfo) ); - BOOST_CHECK_NO_THROW( parseArgs(info.arguments()) ); + BOOST_CHECK_NO_THROW( parseArgs(commands.back().arguments()) ); BOOST_CHECK( parser.parse("foo a b c", &setInfo) ); - BOOST_CHECK_THROW( parseArgs(info.arguments()), senf::console::SyntaxErrorException ); + BOOST_CHECK_THROW( parseArgs(commands.back().arguments()), + senf::console::SyntaxErrorException ); - senf::console::CheckedArgumentIteratorWrapper arg (info.arguments()); - BOOST_CHECK( arg == info.arguments().begin() ); - BOOST_CHECK( arg != info.arguments().end() ); + senf::console::CheckedArgumentIteratorWrapper arg (commands.back().arguments()); + BOOST_CHECK( arg == commands.back().arguments().begin() ); + BOOST_CHECK( arg != commands.back().arguments().end() ); BOOST_CHECK( arg ); ++ arg; BOOST_CHECK( arg ); @@ -238,7 +292,23 @@ BOOST_AUTO_UNIT_TEST(checkedArgumentIterator) BOOST_CHECK( arg.done() ); senf::console::ParseCommandInfo::ArgumentIterator i (arg); - BOOST_CHECK( i == info.arguments().end() ); + BOOST_CHECK( i == commands.back().arguments().end() ); + + commands.clear(); +} + +BOOST_AUTO_UNIT_TEST(parseIncremental) +{ + senf::console::CommandParser parser; + + BOOST_CHECK_EQUAL( parser.parseIncremental("foo a", &setInfo), 0u ); + BOOST_CHECK_EQUAL( parser.parseIncremental("foo a; cd", &setInfo), 7u ); + BOOST_CHECK_EQUAL( parser.parseIncremental("foo a; cd /bar", &setInfo), 7u ); + BOOST_CHECK_EQUAL( parser.parseIncremental("foo a; cd /bar; ", &setInfo), 16u ); + BOOST_CHECK_EQUAL( parser.parseIncremental(" ", &setInfo), 1u ); + BOOST_CHECK_EQUAL( commands.size(), 4u ); + + commands.clear(); } ///////////////////////////////cc.e//////////////////////////////////////// diff --git a/Console/Readline.cci b/Console/Readline.cci index da2842c..200da7c 100644 --- a/Console/Readline.cci +++ b/Console/Readline.cci @@ -53,8 +53,12 @@ prefix_ int senf::console::detail::ReadlineClientReader::getc() prefix_ void senf::console::detail::ReadlineClientReader::write(std::string text) { - translate(text); - handle().write(text); + try { + translate(text); + handle().write(text); + } catch (SystemException &) { + ; + } } prefix_ void senf::console::detail::ReadlineClientReader::terminate() diff --git a/Console/Server.cc b/Console/Server.cc index eecc1d6..310e341 100644 --- a/Console/Server.cc +++ b/Console/Server.cc @@ -103,7 +103,7 @@ prefix_ senf::console::Server & senf::console::Server::start(ServerHandle handle } prefix_ senf::console::Server::Server(ServerHandle handle) - : handle_ (handle) + : handle_ (handle), mode_ (Automatic) { Scheduler::instance().add( handle_, senf::membind(&Server::newClient, this) ); } @@ -116,7 +116,7 @@ prefix_ senf::console::Server::~Server() prefix_ void senf::console::Server::newClient(Scheduler::EventId event) { ServerHandle::ClientSocketHandle client (handle_.accept()); - boost::intrusive_ptr p (new Client(*this, client, name_)); + boost::intrusive_ptr p (new Client(*this, client)); clients_.insert( p ); SENF_LOG(( "Registered new client " << p.get() )); } @@ -190,16 +190,85 @@ prefix_ void senf::console::detail::DumbClientReader::v_translate(std::string & {} /////////////////////////////////////////////////////////////////////////// +// senf::console::detail::NoninteractiveClientReader + +prefix_ +senf::console::detail::NoninteractiveClientReader::NoninteractiveClientReader(Client & client) + : ClientReader (client), binding_ (handle(), + senf::membind(&NoninteractiveClientReader::newData, this), + senf::Scheduler::EV_READ) +{} + +prefix_ void senf::console::detail::NoninteractiveClientReader::v_disablePrompt() +{} + +prefix_ void senf::console::detail::NoninteractiveClientReader::v_enablePrompt() +{} + +prefix_ void senf::console::detail::NoninteractiveClientReader::v_translate(std::string & data) +{} + +prefix_ void +senf::console::detail::NoninteractiveClientReader::newData(senf::Scheduler::EventId event) +{ + if (event != senf::Scheduler::EV_READ || handle().eof()) { + if (! buffer_.empty()) + handleInput(buffer_); + stopClient(); + return; + } + + std::string::size_type n (buffer_.size()); + buffer_.resize(n + handle().available()); + buffer_.erase(handle().read(boost::make_iterator_range(buffer_.begin()+n, buffer_.end())), + buffer_.end()); + buffer_.erase(0, handleInput(buffer_, true)); + stream() << std::flush; +} + +/////////////////////////////////////////////////////////////////////////// // senf::console::Client -prefix_ senf::console::Client::Client(Server & server, ClientHandle handle, - std::string const & name) +prefix_ senf::console::Client::Client(Server & server, ClientHandle handle) : out_t(boost::ref(*this)), senf::log::IOStreamTarget(out_t::member), server_ (server), - handle_ (handle), name_ (name), reader_ (new detail::SafeReadlineClientReader (*this)) + handle_ (handle), + binding_ (handle, boost::bind(&Client::setNoninteractive,this), Scheduler::EV_READ, false), + timer_ (Scheduler::instance().eventTime() + ClockService::milliseconds(INTERACTIVE_TIMEOUT), + boost::bind(&Client::setInteractive, this), false), + name_ (server.name()), reader_ (), mode_ (server.mode()) { - executor_.autocd(true).autocomplete(true); handle_.facet().nodelay(); - // route< senf::SenfLog, senf::log::NOTICE >(); + switch (mode_) { + case Server::Interactive : + setInteractive(); + break; + case Server::Noninteractive : + setNoninteractive(); + break; + case Server::Automatic : + binding_.enable(); + timer_.enable(); + break; + } +} + +prefix_ void senf::console::Client::setInteractive() +{ + SENF_LOG(("Set client interactive")); + binding_.disable(); + timer_.disable(); + mode_ = Server::Interactive; + reader_.reset(new detail::SafeReadlineClientReader (*this)); + executor_.autocd(true).autocomplete(true); +} + +prefix_ void senf::console::Client::setNoninteractive() +{ + SENF_LOG(("Set client non-interactive")); + binding_.disable(); + timer_.disable(); + mode_ = Server::Noninteractive; + reader_.reset(new detail::NoninteractiveClientReader(*this)); } prefix_ void senf::console::Client::translate(std::string & data) @@ -207,17 +276,29 @@ prefix_ void senf::console::Client::translate(std::string & data) reader_->translate(data); } -prefix_ void senf::console::Client::handleInput(std::string data) +prefix_ std::string::size_type senf::console::Client::handleInput(std::string data, + bool incremental) { - if (data.empty()) + SENF_LOG(("Data: " << data)); + + if (data.empty() && ! incremental) data = lastCommand_; else lastCommand_ = data; + bool state (true); + std::string::size_type n (data.size()); + try { - if (! parser_.parse(data, boost::bind( boost::ref(executor_), - boost::ref(stream()), - _1 )) ) + if (incremental) + n = parser_.parseIncremental(data, boost::bind( boost::ref(executor_), + boost::ref(stream()), + _1 )); + else + state = parser_.parse(data, boost::bind( boost::ref(executor_), + boost::ref(stream()), + _1 )); + if (! state ) stream() << "syntax error" << std::endl; } catch (Executor::ExitException &) { @@ -226,7 +307,6 @@ prefix_ void senf::console::Client::handleInput(std::string data) // are called from the client reader callback and that will continue executing even if we // call stop here ... handle_.facet().shutdown(senf::TCPSocketProtocol::ShutRD); - return; } catch (std::exception & ex) { stream() << ex.what() << std::endl; @@ -234,6 +314,7 @@ prefix_ void senf::console::Client::handleInput(std::string data) catch (...) { stream() << "unidentified error (unknown exception thrown)" << std::endl; } + return n; } prefix_ void senf::console::Client::v_write(senf::log::time_type timestamp, @@ -254,13 +335,18 @@ prefix_ std::ostream & senf::console::operator<<(std::ostream & os, Client const typedef ClientSocketHandle< MakeSocketPolicy< INet6AddressingPolicy,ConnectedCommunicationPolicy>::policy > V6Socket; - if (check_socket_cast(client.handle())) - os << dynamic_socket_cast(client.handle()).peer(); - else if (check_socket_cast(client.handle())) - os << dynamic_socket_cast(client.handle()).peer(); - else - os << static_cast(&client); - + try { + if (check_socket_cast(client.handle())) + os << dynamic_socket_cast(client.handle()).peer(); + else if (check_socket_cast(client.handle())) + os << dynamic_socket_cast(client.handle()).peer(); + else + os << static_cast(&client); + } + catch (SystemException &) { + os << "0.0.0.0:0"; + } + return os; } diff --git a/Console/Server.cci b/Console/Server.cci index 427e8ea..405347c 100644 --- a/Console/Server.cci +++ b/Console/Server.cci @@ -52,6 +52,30 @@ prefix_ senf::console::Server & senf::console::Server::name(std::string const & return *this; } +prefix_ std::string const & senf::console::Server::name() + const +{ + return name_; +} + +prefix_ senf::console::Server & senf::console::Server::root(DirectoryNode & root) +{ + root_ = root.thisptr(); + return *this; +} + +prefix_ senf::console::Server & senf::console::Server::mode(Mode m) +{ + mode_ = m; + return *this; +} + +prefix_ senf::console::Server::Mode senf::console::Server::mode() + const +{ + return mode_; +} + prefix_ void senf::console::Server::stop() { // commit suicide @@ -62,7 +86,9 @@ prefix_ void senf::console::Server::stop() // senf::console::Client prefix_ senf::console::Client::~Client() -{} +{ + stream() << std::flush; +} prefix_ void senf::console::Client::stop() { @@ -82,6 +108,18 @@ prefix_ std::string senf::console::Client::promptString() return name_ + ":" + executor_.cwdPath() + "$ "; } +prefix_ senf::console::DirectoryNode & senf::console::Client::root() + const +{ + return server_.root(); +} + +prefix_ senf::console::Server::Mode senf::console::Client::mode() + const +{ + return mode_; +} + prefix_ senf::console::Client & senf::console::Client::get(std::ostream & os) { return dynamic_cast(os)->client(); @@ -133,10 +171,11 @@ prefix_ void senf::console::detail::ClientReader::stopClient() client().stop(); } -prefix_ void senf::console::detail::ClientReader::handleInput(std::string const & input) +prefix_ std::string::size_type +senf::console::detail::ClientReader::handleInput(std::string const & input, bool incremental) const { - client().handleInput(input); + return client().handleInput(input, incremental); } prefix_ void senf::console::detail::ClientReader::disablePrompt() diff --git a/Console/Server.hh b/Console/Server.hh index 0216840..52925d0 100644 --- a/Console/Server.hh +++ b/Console/Server.hh @@ -35,6 +35,8 @@ #include "../Socket/Protocols/INet/TCPSocketHandle.hh" #include "../Socket/ServerSocketHandle.hh" #include "../Scheduler/Scheduler.hh" +#include "../Scheduler/Binding.hh" +#include "../Scheduler/Timer.hh" #include "../Scheduler/ReadHelper.hh" #include "Parse.hh" #include "Executor.hh" @@ -80,17 +82,50 @@ namespace console { typedef detail::ServerHandle ServerHandle; + enum Mode { Automatic, Interactive, Noninteractive }; + + /////////////////////////////////////////////////////////////////////////// + ~Server(); static Server & start(senf::INet4SocketAddress const & address); ///< Start server on given IPv4 address/port static Server & start(senf::INet6SocketAddress const & address); ///< Start server on given IPv6 address/port + + std::string const & name() const; ///< Get server name + /**< This information is used in the prompt string. */ + Server & name(std::string const & name); ///< Set server name /**< This information is used in the prompt string. */ + + DirectoryNode & root() const; ///< Get root node + + Server & root(DirectoryNode & root); ///< Set root node + /**< \a node will be the root node for all clients launched + from this server. */ + + Mode mode() const; ///< Get mode + /**< \see \ref mode(Mode) */ + Server & mode(Mode mode); ///< Set mode + /**< There are two Server types: + \li An interactive server displays a command prompt and + optionally supports command-line editing. + \li A non-interactive server does not display any + prompt and does not allow any interactive + editing. This type of server is used for (remote) + scripting. + + The \a mode parameter selects between these modes. In + \c Automatic (the default), a client connection is + considered to be interactive if there is no data + traffic in the first 500ms after the connection is + opened. */ + void stop(); ///< Stop the server /**< All clients will be closed */ + protected: @@ -104,6 +139,8 @@ namespace console { void removeClient(Client & client); ServerHandle handle_; + DirectoryNode::ptr root_; + Mode mode_; typedef std::set< boost::intrusive_ptr > Clients; Clients clients_; @@ -130,6 +167,8 @@ namespace console { SENF_LOG_CLASS_AREA(); SENF_LOG_DEFAULT_LEVEL( senf::log::NOTICE ); + static const unsigned INTERACTIVE_TIMEOUT = 500; // milliseconds; + public: typedef Server::ServerHandle::ClientSocketHandle ClientHandle; @@ -142,27 +181,35 @@ namespace console { ClientHandle handle() const; std::ostream & stream(); std::string promptString() const; + DirectoryNode & root() const; + Server::Mode mode() const; static Client & get(std::ostream & os); protected: private: - Client(Server & server, ClientHandle handle, std::string const & name); + Client(Server & server, ClientHandle handle); + void setInteractive(); + void setNoninteractive(); + void translate(std::string & data); - void handleInput(std::string input); + unsigned handleInput(std::string input, bool incremental = false); virtual void v_write(senf::log::time_type timestamp, std::string const & stream, std::string const & area, unsigned level, std::string const & message); Server & server_; ClientHandle handle_; + SchedulerBinding binding_; + SchedulerTimer timer_; CommandParser parser_; Executor executor_; std::string name_; std::string lastCommand_; boost::scoped_ptr reader_; + Server::Mode mode_; friend class Server; friend class detail::ClientReader; diff --git a/Console/Server.ih b/Console/Server.ih index 6bbaf5a..d31f713 100644 --- a/Console/Server.ih +++ b/Console/Server.ih @@ -90,7 +90,7 @@ namespace detail { // Called by subclasses to perform actions in the Client void stopClient(); - void handleInput(std::string const & input) const; + std::string::size_type handleInput(std::string const & input, bool incremental=false) const; // Called by the Client @@ -132,6 +132,28 @@ namespace detail { unsigned promptLen_; bool promptActive_; }; + + /** \brief Internal: Primitive ClientReader implementation + + This implementation uses the cooked telnet mode to read lines from the console. It does not + support explicit line editing or any other advanced features. + */ + class NoninteractiveClientReader + : public ClientReader + { + public: + NoninteractiveClientReader(Client & client); + + private: + virtual void v_disablePrompt(); + virtual void v_enablePrompt(); + virtual void v_translate(std::string & data); + + void newData(senf::Scheduler::EventId event); + + SchedulerBinding binding_; + std::string buffer_; + }; }}} diff --git a/Scheduler/Timer.cci b/Scheduler/Timer.cci new file mode 100644 index 0000000..dad761c --- /dev/null +++ b/Scheduler/Timer.cci @@ -0,0 +1,88 @@ +// $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 Timer inline non-template implementation */ + +//#include "Timer.ih" + +// Custom includes + +#define prefix_ inline +///////////////////////////////cci.p/////////////////////////////////////// + +prefix_ senf::SchedulerTimer::SchedulerTimer(ClockService::clock_type timeout, + Scheduler::SimpleCallback const & cb, + bool enabled) + : timeout_ (timeout), cb_ (cb), + id_ (enabled ? Scheduler::instance().timeout(timeout_, cb_) : 0), + enabled_ (enabled) +{} + +prefix_ void senf::SchedulerTimer::enable() +{ + if (!enabled_) { + id_ = Scheduler::instance().timeout(timeout_, cb_); + enabled_ = true; + } +} + +prefix_ void senf::SchedulerTimer::disable() +{ + if (enabled_) { + Scheduler::instance().cancelTimeout(id_); + enabled_ = false; + } +} + +prefix_ bool senf::SchedulerTimer::enabled() +{ + return enabled_; +} + +prefix_ void senf::SchedulerTimer::update(ClockService::clock_type timeout) +{ + if (enabled_) + Scheduler::instance().cancelTimeout(id_); + timeout_ = timeout; + if (enabled_) + Scheduler::instance().timeout(timeout_, cb_); +} + +prefix_ senf::SchedulerTimer::~SchedulerTimer() +{ + disable(); +} + +///////////////////////////////cci.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/Scheduler/Timer.hh b/Scheduler/Timer.hh new file mode 100644 index 0000000..93dbf43 --- /dev/null +++ b/Scheduler/Timer.hh @@ -0,0 +1,108 @@ +// $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 Timer public header */ + +#ifndef HH_Timer_ +#define HH_Timer_ 1 + +// Custom includes +#include "Scheduler.hh" + +//#include "Timer.mpp" +///////////////////////////////hh.p//////////////////////////////////////// + +namespace senf { + + /** \brief Manage scheduler timer + + This class will manage a single timer: The timer can be enabled, disabled and updated and + will automatically be removed, when this instance is destroyed. + + \code + class Foo + { + public: + Foo() : timer_ ( ClockServer::now() + ClockService::milliseconds(500), + senf::membind(&Foo::timer, this) ) {} + + void blarf() { timer_.disable(); } + + private: + void timer(); + + senf::SchedulerTimer timer_; + }; + \endcode + */ + class SchedulerTimer + : boost::noncopyable + { + public: + /////////////////////////////////////////////////////////////////////////// + ///\name Structors and default members + ///@{ + + SchedulerTimer(ClockService::clock_type timeout, Scheduler::SimpleCallback const & cb, + bool enabled=true); + ~SchedulerTimer(); + + ///@} + /////////////////////////////////////////////////////////////////////////// + + void enable(); ///< Enable timer + void disable(); ///< Disable timer + bool enabled(); ///< \c true, if timer is currently enabled + /**< An expired timer can still be in enabled state. */ + + void update(ClockService::clock_type timeout); ///< Change timeout time and enable timer + /**< If the timer is not enabled, you need to call enable() + for the timer to become effective. */ + + protected: + + private: + ClockService::clock_type timeout_; + Scheduler::SimpleCallback cb_; + unsigned id_; + bool enabled_; + }; + +} + +///////////////////////////////hh.e//////////////////////////////////////// +#include "Timer.cci" +//#include "Timer.ct" +//#include "Timer.cti" +#endif + + +// 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/Socket/ClientSocketHandle.hh b/Socket/ClientSocketHandle.hh index 2d15dc9..ee23d52 100644 --- a/Socket/ClientSocketHandle.hh +++ b/Socket/ClientSocketHandle.hh @@ -427,13 +427,13 @@ namespace senf { void state(SocketStateMap & map, unsigned lod=0); std::string dumpState(unsigned lod=0); + unsigned available(); + protected: ClientSocketHandle(FileHandle other, bool isChecked); explicit ClientSocketHandle(std::auto_ptr body); private: - unsigned available(); - friend class senf::ServerSocketHandle; };