Parse.cc
Go to the documentation of this file.
1 //
2 // Copyright (c) 2020 Fraunhofer Institute for Applied Information Technology (FIT)
3 // Network Research Group (NET)
4 // Schloss Birlinghoven, 53754 Sankt Augustin, GERMANY
5 // Contact: support@wiback.org
6 //
7 // This file is part of the SENF code tree.
8 // It is licensed under the 3-clause BSD License (aka New BSD License).
9 // See LICENSE.txt in the top level directory for details or visit
10 // https://opensource.org/licenses/BSD-3-Clause
11 //
12 
13 
17 #include "Parse.hh"
18 #include "Parse.ih"
19 
20 // Custom includes
21 #include <cerrno>
22 #include <sys/stat.h>
23 #include <boost/iterator/transform_iterator.hpp>
24 #include <senf/config.hh>
25 
26 #include <boost/spirit/include/classic_file_iterator.hpp>
27 #include <boost/spirit/include/classic_position_iterator.hpp>
28 
29 #include <senf/Utils/Exception.hh>
30 #include <senf/Utils/senfassert.hh>
31 
32 //#include "Parse.mpp"
33 #define prefix_
34 //-/////////////////////////////////////////////////////////////////////////////////////////////////
35 
36 namespace senf {
37 namespace console {
38 namespace detail {
39 
40 #ifndef DOXYGEN
41 
42  struct ParseDispatcher
43  {
44  ParseCommandInfo * info_;
45 
46  struct BindInfo {
47  BindInfo( ParseDispatcher & d, ParseCommandInfo & info)
48  : dispatcher (d) { dispatcher.info_ = &info; }
49  ~BindInfo() { dispatcher.info_ = 0; }
50 
51  ParseDispatcher & dispatcher;
52  };
53 
54  void beginCommand(std::vector<Token> & command)
55  { info_->clear();
56  info_->command(command); }
57 
58  void endCommand()
59  { }
60 
61  void pushToken(Token const & token)
62  { info_->addToken(token); }
63 
64  void builtin_cd(std::vector<Token> & path)
65  { info_->clear();
66  info_->builtin(ParseCommandInfo::BuiltinCD);
67  setBuiltinPathArg(path); }
68 
69  void builtin_ls(std::vector<Token> & path)
70  { info_->clear();
71  info_->builtin(ParseCommandInfo::BuiltinLS);
72  setBuiltinPathArg(path); }
73 
74  void builtin_ll(std::vector<Token> & path)
75  { info_->clear();
76  info_->builtin(ParseCommandInfo::BuiltinLL);
77  setBuiltinPathArg(path); }
78 
79  void builtin_lr(std::vector<Token> & path)
80  { info_->clear();
81  info_->builtin(ParseCommandInfo::BuiltinLR);
82  setBuiltinPathArg(path); }
83 
84  void pushDirectory()
85  { // Do NOT call clear since pushDirectory is set in ADDITION
86  // to an ordinary command (which may be only a directory name)
87  info_->builtin(ParseCommandInfo::BuiltinPUSHD); }
88 
89  void popDirectory()
90  { info_->clear();
91  info_->builtin(ParseCommandInfo::BuiltinPOPD); }
92 
93  void builtin_exit()
94  { info_->clear();
95  info_->builtin(ParseCommandInfo::BuiltinEXIT); }
96 
97  void builtin_help(std::vector<Token> & path)
98  { info_->clear();
99  info_->builtin(ParseCommandInfo::BuiltinHELP);
100  setBuiltinPathArg(path); }
101 
102  void builtin_echo()
103  { info_->builtin(ParseCommandInfo::BuiltinECHO); }
104 
105  void setBuiltinPathArg(std::vector<Token> & path)
106  {
107  info_->command(path);
108 // pushToken(ArgumentGroupOpenToken());
109 // for (std::vector<Token>::const_iterator i (path.begin());
110 // i != path.end(); ++i)
111 // pushToken(*i);
112 // pushToken(ArgumentGroupCloseToken());
113  }
114  };
115 
116 #endif
117 
118 }}}
119 
120 //-/////////////////////////////////////////////////////////////////////////////////////////////////
121 // senf::console::Token
122 
124  detail::FilePositionWithIndex const & pos)
125  : type_(type), token_ (token), line_ (pos.line), column_ (pos.column), index_ (pos.index)
126 {}
127 
128 
129 prefix_ std::ostream & senf::console::operator<<(std::ostream & os, Token const & token)
130 {
131  static char const * tokenTypeName[] = {
132  "None",
133  "PathSeparator",
134  "ArgumentGroupOpen",
135  "ArgumentGroupClose",
136  "DirectoryGroupOpen",
137  "DirectoryGroupClose",
138  "CommandTerminator",
139  "OtherPunctuation",
140  "BasicString",
141  "HexString",
142  "Word" };
143  // The real table is:
144  // static const int bitPosition[32] = {
145  // 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
146  // 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 };
147  // However, we have replaced all values >= sizeof(tokenTypeName) with 0
148  // and have added 1 to all the remaining values
149  static const int bitPosition[32] = {
150  1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 5, 9,
151  0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 7, 0, 6, 0, 10 };
152  // We need to check token.type() against 0 explicitly since 0 and 1 will both be mapped to 0
153  os << tokenTypeName[ token.type()
154  ? bitPosition[(((token.type() & -token.type()) * 0x077CB531UL) >> 27) & 31]
155  : 0 ]
156  << "('"
157  << token.value()
158  << "')";
159  return os;
160 }
161 
162 //-/////////////////////////////////////////////////////////////////////////////////////////////////
163 // senf::console::ParseCommandInfo
164 
165 prefix_ std::ostream & senf::console::operator<<(std::ostream & stream,
166  ParseCommandInfo const & info)
167 {
168  if (info.builtin() == ParseCommandInfo::NoBuiltin) {
169  ParseCommandInfo::TokensRange::const_iterator i (info.commandPath().begin());
170  ParseCommandInfo::TokensRange::const_iterator const i_end (info.commandPath().end());
171  if (i != i_end) {
172  for (;;) {
173  stream << i->value();
174  if ( ++i != i_end ) stream << "/";
175  else break;
176  }
177  }
178  }
179  else {
180  char const * builtins[] = { 0, "cd", "ls", "lr", "pushd", "popd", "exit", "help" };
181  stream << "builtin-" << builtins[info.builtin()];
182  }
183 
185  for (ParseCommandInfo::argument_iterator i (args.begin()); i != args.end(); ++i) {
186  ParseCommandInfo::token_iterator j (i->begin());
187  stream << " [";
188  if ( j != i->end() ) {
189  for (;;) {
190  stream << "'" << j->value() << "'";
191  if ( ++j != i->end() ) stream << ' ';
192  else break;
193  }
194  }
195  stream << "]";
196  }
197 
198  return stream;
199 }
200 
201 //-/////////////////////////////////////////////////////////////////////////////////////////////////
202 // senf::console::ParseCommandInfo::ArgumentIterator
203 
204 prefix_ void senf::console::ParseCommandInfo::ArgumentIterator::setRange()
205  const
206 {
207  if (b_->is(Token::ArgumentGroupOpen)) {
208  unsigned level (0);
209  e_ = b_;
210  for (;;) {
211  if (e_->is(Token::ArgumentGroupOpen))
212  ++ level;
213  else if (e_->is(Token::ArgumentGroupClose)) {
214  -- level;
215  if (level == 0)
216  break;
217  }
218  ++e_;
219  }
220  }
221  ++ e_;
222 }
223 
224 prefix_ void senf::console::ParseCommandInfo::ArgumentIterator::decrement()
225 {
226  e_ = b_;
227  --b_;
228  if (b_->is(Token::ArgumentGroupClose)) {
229  unsigned level (0);
230  for (;;) {
231  if (b_->is(Token::ArgumentGroupClose))
232  ++ level;
233  else if (b_->is(Token::ArgumentGroupOpen)) {
234  -- level;
235  if (level == 0)
236  break;
237  }
238  --b_;
239  }
240  }
241 }
242 
243 //-/////////////////////////////////////////////////////////////////////////////////////////////////
244 // senf::console::CommandParser
245 
246 #ifndef DOXYGEN
247 
248 struct senf::console::CommandParser::Impl
249 {
250  typedef detail::CommandGrammar<detail::ParseDispatcher> Grammar;
251 
252  detail::ParseDispatcher dispatcher;
253  Grammar::Context context;
254  Grammar grammar;
255 
256  Impl() : dispatcher(), context(), grammar(dispatcher, context) {}
257 };
258 
259 #endif
260 
261 namespace {
262 
263  template <class Error>
264  void throwParserError(Error const & err)
265  {
266  static char const * msg [] = { "end of statement expected",
267  "path expected",
268  "')' expected",
269  "'\"' expected" };
270  senf::console::detail::FilePositionWithIndex pos (err.where.get_position());
271  throw senf::console::CommandParser::ParserErrorException(msg[err.descriptor])
272  << "\nat " << pos.file << ":" << pos.line << ":" << pos.column;
273  }
274 
275 }
276 
277 namespace boost {
278 namespace spirit {
279 namespace classic {
280 
281  template <>
282  class position_policy<senf::console::detail::FilePositionWithIndex>
283  : public position_policy<file_position>
284  {
285  public:
286  typedef position_policy<file_position> Base;
287 
288  void next_line(senf::console::detail::FilePositionWithIndex & pos)
289  {
290  Base::next_line(pos);
291  pos.index ++;
292  }
293 
294  void next_char(senf::console::detail::FilePositionWithIndex & pos)
295  {
296  Base::next_char(pos);
297  pos.index ++;
298  }
299 
300  void tabulation(senf::console::detail::FilePositionWithIndex & pos)
301  {
302  Base::tabulation(pos);
303  pos.index ++;
304  }
305  };
306 
307 }}}
308 
310  : impl_ (new Impl())
311 {}
312 
314 {}
315 
316 // This template member is placed here, since it is ONLY called from the implementation. Otherwise,
317 // we would need to expose the Impl member to the public, which we don't want to do.
318 
319 template <class Iterator>
320 prefix_ Iterator senf::console::CommandParser::parseLoop(Iterator npb, Iterator npe,
321  std::string const & source, Callback cb)
322 {
323  typedef detail::boost_spirit::position_iterator<
324  Iterator, detail::FilePositionWithIndex> PositionIterator;
325  PositionIterator b (npb, npe, source);
326  PositionIterator e (npe, npe, source);
327  ParseCommandInfo info;
328  detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info);
329  detail::boost_spirit::parse_info<PositionIterator> result;
330 
331  for (;;) {
333  b, e, * impl().grammar.use_parser<Impl::Grammar::SkipParser>());
334  b = result.stop;
335  if (b == e)
336  return e.base();
337  info.clear();
338  try {
339  result = detail::boost_spirit::parse(b, e,
340  impl().grammar.use_parser<Impl::Grammar::CommandParser>(),
341  impl().grammar.use_parser<Impl::Grammar::SkipParser>());
342  }
343  catch (detail::boost_spirit::parser_error<Impl::Grammar::Errors, PositionIterator> & ex) {
344  if (impl().grammar.incremental && ex.where == e)
345  return b.base();
346  else
347  throwParserError(ex);
348  }
349  // Otherwise the error handling in the parser is broken
350  SENF_ASSERT( result.hit, "Internal parser failure (error handling broken?)" );
351  if (! info.empty())
352  try {
353  cb(info);
354  }
355  catch (senf::ExceptionMixin & ex) {
356  detail::FilePositionWithIndex pos (result.stop.get_position());
357  ex << "\nat " << pos.file << ":" << pos.line << ":" << pos.column;
358  throw;
359  }
360  b = result.stop;
361  }
362 }
363 
364 prefix_ void senf::console::CommandParser::parse(std::string const & command, Callback cb)
365 {
366  parseLoop(command.begin(), command.end(), "<unknown>", cb);
367 }
368 
369 prefix_ void senf::console::CommandParser::parseFile(std::string const & filename, Callback cb)
370 {
371  // file_iterator sets errno to EINVAL and returns error when file size is 0
372  // so we check the file size before
373  struct stat statBuf;
374  if (stat( filename.c_str(), &statBuf) != 0)
375  throw SystemException(filename, errno SENF_EXC_DEBUGINFO);
376  if (statBuf.st_size == 0) return;
377  detail::boost_spirit::file_iterator<> i (filename);
378  if (!i) {
379  if (errno == 0)
380  // hmm.. errno==0 but the file_iterator is false; something is wrong but we
381  // do not know what exactly, so we throw a SystemeException with EINVAL
382  throw SystemException(filename, EINVAL SENF_EXC_DEBUGINFO);
383  else
384  throw SystemException(filename, errno SENF_EXC_DEBUGINFO);
385  }
386  detail::boost_spirit::file_iterator<> const i_end (i.make_end());
387  parseLoop(i, i_end, filename, cb);
388 }
389 
390 prefix_ void senf::console::CommandParser::parseArguments(std::string const & arguments,
391  ParseCommandInfo & info)
392 {
393  typedef detail::boost_spirit::position_iterator<
394  std::string::const_iterator, detail::FilePositionWithIndex> PositionIterator;
395  PositionIterator b (arguments.begin(), arguments.end(), std::string("<unknown>"));
396  PositionIterator e (arguments.end(), arguments.end(), std::string("<unknown>"));
397  detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info);
398  detail::boost_spirit::parse_info<PositionIterator> result;
399  try {
400  result = detail::boost_spirit::parse( b, e,
401  impl().grammar.use_parser<Impl::Grammar::ArgumentsParser>(),
402  impl().grammar.use_parser<Impl::Grammar::SkipParser>() );
403  }
404  catch (detail::boost_spirit::parser_error<Impl::Grammar::Errors, PositionIterator> & ex) {
405  throwParserError(ex);
406  }
407  if (! result.full) {
408  detail::FilePositionWithIndex pos (result.stop.get_position());
409  throw ParserErrorException("argument expected")
410  << "\nat " << pos.file << ":" << pos.line << ":" << pos.column;
411  }
412 }
413 
414 prefix_ void senf::console::CommandParser::parsePath(std::string const & path,
415  ParseCommandInfo & info)
416 {
417  typedef detail::boost_spirit::position_iterator<
418  std::string::const_iterator, detail::FilePositionWithIndex> PositionIterator;
419  PositionIterator b (path.begin(), path.end(), std::string("<unknown>"));
420  PositionIterator e (path.end(), path.end(), std::string("<unknown>"));
421  detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info);
422  detail::boost_spirit::parse_info<PositionIterator> result;
423  try {
424  result = detail::boost_spirit::parse( b, e,
425  impl().grammar.use_parser<Impl::Grammar::PathParser>(),
426  impl().grammar.use_parser<Impl::Grammar::SkipParser>() );
427  }
428  catch (detail::boost_spirit::parser_error<Impl::Grammar::Errors, PositionIterator> & ex) {
429  throwParserError(ex);
430  }
431  if (! result.full) {
432  detail::FilePositionWithIndex pos (result.stop.get_position());
433  throw ParserErrorException("path expected")
434  << "\nat " << pos.file << ":" << pos.line << ":" << pos.column;
435  }
436 }
437 
439 {
441  parser_.impl().grammar.incremental = true;
442  }
443 
445  parser_.impl().grammar.incremental = false;
446  }
447 
449 };
450 
451 prefix_ std::string::size_type
453 {
454  SetIncremental si (*this);
455  return std::distance( commands.begin(),
456  parseLoop(commands.begin(), commands.end(), "<unknown>", cb) );
457 }
458 
459 //-/////////////////////////////////////////////////////////////////////////////////////////////////
460 // Character sets
461 
463 {
464  return Impl::Grammar::special_p().test(ch);
465 }
466 
468 {
469  return Impl::Grammar::punctuation_p().test(ch);
470 }
471 
473 {
474  return Impl::Grammar::space_p().test(ch);
475 }
476 
478 {
479  return Impl::Grammar::invalid_p().test(ch);
480 }
481 
483 {
484  return Impl::Grammar::word_p().test(ch);
485 }
486 
487 //-/////////////////////////////////////////////////////////////////////////////////////////////////
488 #undef prefix_
489 //#include "Parse.mpp"
490 
491 
492 // Local Variables:
493 // mode: c++
494 // fill-column: 100
495 // comment-column: 40
496 // c-file-style: "senf"
497 // indent-tabs-mode: nil
498 // ispell-local-dictionary: "american"
499 // compile-command: "scons -u test"
500 // End:
Exception thrown when the parser detects an error.
Definition: Parse.hh:656
boost::iterator_range< argument_iterator > ArgumentsRange
Definition: Parse.hh:377
std::string::size_type parseIncremental(std::string const &commands, Callback cb)
Incremental parse.
Definition: Parse.cc:452
Definition: Parse.cc:277
Parse commands.
Definition: Parse.hh:602
Token()
Create empty token.
#define prefix_
Definition: Parse.cc:33
unspecified_keyword_type parser
Argument parser.
Tokens::const_iterator token_iterator
Definition: Parse.hh:372
void clear()
Clear all data members.
void parse(std::string const &command, Callback cb)
Parse string.
Definition: Parse.cc:364
static bool isInvalidChar(char ch)
Check, if ch is an invalid character.
Definition: Parse.cc:477
boost::function< void(ParseCommandInfo const &)> Callback
Definition: Parse.hh:609
Iterator parsing argument groups.
Definition: Parse.hh:442
static bool isWordChar(char ch)
Check, if ch is a word character.
Definition: Parse.cc:482
bool empty()
true, if the data is empty
Definition: Config.hh:28
void parseFile(std::string const &filename, Callback cb)
Parse file.
Definition: Parse.cc:369
std::ostream & operator<<(std::ostream &os, Token const &token)
Definition: Parse.cc:129
Single parsed console command.
Definition: Parse.hh:363
TokensRange commandPath() const
Command path.
SetIncremental(CommandParser &parser)
Definition: Parse.cc:440
void parse(ParseCommandInfo::TokensRange const &tokens, Type &out)
Parse token range.
TokenType type() const
Token type.
BuiltinCommand builtin() const
Command type.
std::string const & value() const
String value of token.
static bool isSpecialChar(char ch)
Check, if ch is a special character.
Definition: Parse.cc:462
#define SENF_ASSERT(x, comment)
ArgumentsRange arguments() const
Command arguments.
#define SENF_EXC_DEBUGINFO
Parse public header.
void parseArguments(std::string const &arguments, ParseCommandInfo &info)
Parse arguments.
Definition: Parse.cc:390
static bool isPunctuationChar(char ch)
Check, if ch is a punctuation character.
Definition: Parse.cc:467
void parsePath(std::string const &path, ParseCommandInfo &info)
Parse path.
Definition: Parse.cc:414
Single argument token.
Definition: Parse.hh:234
static bool isSpaceChar(char ch)
Check, if ch is a space character.
Definition: Parse.cc:472