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;
};
info_->command(command); }
void endCommand()
- { cb_(*info_); }
+ { }
void pushToken(Token const & token)
{ info_->addToken(token); }
void builtin_cd(std::vector<Token> & path)
{ info_->clear();
info_->builtin(ParseCommandInfo::BuiltinCD);
- setBuiltinPathArg(path);
- cb_(*info_); }
+ setBuiltinPathArg(path); }
void builtin_ls(std::vector<Token> & path)
{ info_->clear();
info_->builtin(ParseCommandInfo::BuiltinLS);
- setBuiltinPathArg(path);
- cb_(*info_); }
+ setBuiltinPathArg(path); }
void pushDirectory(std::vector<Token> & 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<Token> & path)
{ info_->clear();
info_->builtin(ParseCommandInfo::BuiltinHELP);
- setBuiltinPathArg(path);
- cb_(*info_); }
+ setBuiltinPathArg(path); }
void setBuiltinPathArg(std::vector<Token> & path)
{
"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()
}
}
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()];
}
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 <class Iterator>
+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::CommandParser>(),
- impl().grammar.use_parser<Impl::Grammar::SkipParser>()
- ).full;
+ detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info);
+ boost::spirit::parse_info<Iterator> result;
+
+ for(;;) {
+ result = boost::spirit::parse(
+ b, e, * impl().grammar.use_parser<Impl::Grammar::SkipParser>());
+ b = result.stop;
+ if (b == e)
+ return e;
+ info.clear();
+ result = boost::spirit::parse(b, e,
+ impl().grammar.use_parser<Impl::Grammar::CommandParser>(),
+ impl().grammar.use_parser<Impl::Grammar::SkipParser>());
+ 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::CommandParser>(),
- impl().grammar.use_parser<Impl::Grammar::SkipParser>()
- ).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::ArgumentsParser>(),
impl().grammar.use_parser<Impl::Grammar::SkipParser>()
).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
tokens_.clear();
}
+prefix_ bool senf::console::ParseCommandInfo::empty()
+{
+ return builtin_ == NoBuiltin && commandPath_.empty();
+}
+
prefix_ void senf::console::ParseCommandInfo::builtin(BuiltinCommand builtin)
{
builtin_ = builtin;
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<Token> & commandPath); ///< Assign non-builtin command
///@}
///////////////////////////////////////////////////////////////////////////
- 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 <class Iterator>
+ Iterator parseLoop(Iterator b, Iterator e, Callback cb);
Impl & impl();
boost::scoped_ptr<Impl> impl_;
+
+ friend class SetIncremental;
};
}}
#include <boost/spirit.hpp>
#include <boost/spirit/utility/grammar_def.hpp>
#include <boost/spirit/actor.hpp>
+#include <boost/spirit/dynamic.hpp>
+#include <boost/spirit/phoenix.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/ref.hpp>
Context & context;
///////////////////////////////////////////////////////////////////////////
+ // Configuration
+
+ bool incremental;
+
+ ///////////////////////////////////////////////////////////////////////////
// Dispatching semantic actions
ParseDispatcher & dispatcher;
///////////////////////////////////////////////////////////////////////////
CommandGrammar(ParseDispatcher & d, Context & c)
- : context(c), dispatcher(d) {}
+ : context(c), incremental(false), dispatcher(d) {}
template <class Scanner>
struct definition
{
boost::spirit::rule<Scanner> 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;
{
using namespace boost::spirit;
+ using namespace phoenix;
typedef ParseDispatcher PD;
typedef Token AT;
// 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
//
// 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
;
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
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) ]
;
[ 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('#')
;
///////////////////////////////////////////////////////////////////
start_parsers(
- commands, // CommandParser
+ command, // CommandParser
skip, // SkipParser
arguments // ArgumentsParser
);
typedef senf::console::detail::CommandGrammar<TestParseDispatcher> 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::CommandParser>(),
- grammar.use_parser<Grammar::SkipParser>() ) . 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::CommandParser>(),
+ grammar.use_parser<Grammar::SkipParser>() ) . 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::CommandParser>(),
+ grammar.use_parser<Grammar::SkipParser>() ) . 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::CommandParser>(),
+ grammar.use_parser<Grammar::SkipParser>() ) . 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::CommandParser>(),
+ grammar.use_parser<Grammar::SkipParser>() ) . full );
+ BOOST_CHECK_EQUAL( ss.str(), "builtin_exit()\n" );
+ }
+
+ {
+ ss.str("");
+ BOOST_CHECK( boost::spirit::parse(
+ "foo/bar/ {",
+ grammar.use_parser<Grammar::CommandParser>(),
+ grammar.use_parser<Grammar::SkipParser>() ) . full );
+ BOOST_CHECK_EQUAL( ss.str(), "pushDirectory( Word('foo')/Word('bar')/None('') )\n" );
+ }
+
+ {
+ ss.str("");
+ BOOST_CHECK( boost::spirit::parse(
+ "}",
+ grammar.use_parser<Grammar::CommandParser>(),
+ grammar.use_parser<Grammar::SkipParser>() ) . full );
+ BOOST_CHECK_EQUAL( ss.str(), "popDirectory()\n" );
+ }
+
+ {
+ ss.str("");
+ BOOST_CHECK( boost::spirit::parse(
+ "help /foo/bar",
+ grammar.use_parser<Grammar::CommandParser>(),
+ grammar.use_parser<Grammar::SkipParser>() ) . full );
+ BOOST_CHECK_EQUAL( ss.str(), "builtin_help( None('')/Word('foo')/Word('bar') )\n" );
+ }
}
namespace {
- senf::console::ParseCommandInfo info;
+ std::vector<senf::console::ParseCommandInfo> commands;
void setInfo(senf::console::ParseCommandInfo const & i)
- { info = i; }
+ { commands.push_back(i); }
}
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 {
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 );
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////////////////////////////////////////
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()
}
prefix_ senf::console::Server::Server(ServerHandle handle)
- : handle_ (handle)
+ : handle_ (handle), mode_ (Automatic)
{
Scheduler::instance().add( handle_, senf::membind(&Server::newClient, this) );
}
prefix_ void senf::console::Server::newClient(Scheduler::EventId event)
{
ServerHandle::ClientSocketHandle client (handle_.accept());
- boost::intrusive_ptr<Client> p (new Client(*this, client, name_));
+ boost::intrusive_ptr<Client> p (new Client(*this, client));
clients_.insert( p );
SENF_LOG(( "Registered new client " << p.get() ));
}
{}
///////////////////////////////////////////////////////////////////////////
+// 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<senf::TCPSocketProtocol>().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)
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<void>( boost::ref(executor_),
- boost::ref(stream()),
- _1 )) )
+ if (incremental)
+ n = parser_.parseIncremental(data, boost::bind<void>( boost::ref(executor_),
+ boost::ref(stream()),
+ _1 ));
+ else
+ state = parser_.parse(data, boost::bind<void>( boost::ref(executor_),
+ boost::ref(stream()),
+ _1 ));
+ if (! state )
stream() << "syntax error" << std::endl;
}
catch (Executor::ExitException &) {
// are called from the client reader callback and that will continue executing even if we
// call stop here ...
handle_.facet<senf::TCPSocketProtocol>().shutdown(senf::TCPSocketProtocol::ShutRD);
- return;
}
catch (std::exception & ex) {
stream() << ex.what() << std::endl;
catch (...) {
stream() << "unidentified error (unknown exception thrown)" << std::endl;
}
+ return n;
}
prefix_ void senf::console::Client::v_write(senf::log::time_type timestamp,
typedef ClientSocketHandle< MakeSocketPolicy<
INet6AddressingPolicy,ConnectedCommunicationPolicy>::policy > V6Socket;
- if (check_socket_cast<V4Socket>(client.handle()))
- os << dynamic_socket_cast<V4Socket>(client.handle()).peer();
- else if (check_socket_cast<V6Socket>(client.handle()))
- os << dynamic_socket_cast<V6Socket>(client.handle()).peer();
- else
- os << static_cast<void const *>(&client);
-
+ try {
+ if (check_socket_cast<V4Socket>(client.handle()))
+ os << dynamic_socket_cast<V4Socket>(client.handle()).peer();
+ else if (check_socket_cast<V6Socket>(client.handle()))
+ os << dynamic_socket_cast<V6Socket>(client.handle()).peer();
+ else
+ os << static_cast<void const *>(&client);
+ }
+ catch (SystemException &) {
+ os << "0.0.0.0:0";
+ }
+
return os;
}
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
// senf::console::Client
prefix_ senf::console::Client::~Client()
-{}
+{
+ stream() << std::flush;
+}
prefix_ void senf::console::Client::stop()
{
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<detail::NonblockingSocketOStream&>(os)->client();
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()
#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"
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:
void removeClient(Client & client);
ServerHandle handle_;
+ DirectoryNode::ptr root_;
+ Mode mode_;
typedef std::set< boost::intrusive_ptr<Client> > Clients;
Clients clients_;
SENF_LOG_CLASS_AREA();
SENF_LOG_DEFAULT_LEVEL( senf::log::NOTICE );
+ static const unsigned INTERACTIVE_TIMEOUT = 500; // milliseconds;
+
public:
typedef Server::ServerHandle::ClientSocketHandle ClientHandle;
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<detail::ClientReader> reader_;
+ Server::Mode mode_;
friend class Server;
friend class detail::ClientReader;
// 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
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_;
+ };
}}}
--- /dev/null
+// $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 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_
+
+\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:
--- /dev/null
+// $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 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
+
+\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:
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<SocketBody> body);
private:
- unsigned available();
-
friend class senf::ServerSocketHandle<SPolicy>;
};