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