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