preprocessor.cpp

Go to the documentation of this file.
00001 /* $Id: preprocessor.cpp 26571 2008-05-12 23:34:38Z suokko $ */
00002 /*
00003    Copyright (C) 2003 by David White <dave@whitevine.net>
00004    Copyright (C) 2005 - 2008 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
00005    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License version 2
00009    or at your option any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 //! @file serialization/preprocessor.cpp
00017 //! WML preprocessor.
00018 
00019 #include "../global.hpp"
00020 
00021 #include "../filesystem.hpp"
00022 #include "../log.hpp"
00023 #include "../wesconfig.h"
00024 #include "preprocessor.hpp"
00025 #include "string_utils.hpp"
00026 
00027 #include <algorithm>
00028 #include <cassert>
00029 #include <iostream>
00030 #include <sstream>
00031 #include <vector>
00032 
00033 #define ERR_CF LOG_STREAM(err, config)
00034 #define LOG_CF LOG_STREAM(info, config)
00035 #define DBG_CF LOG_STREAM(debug, config)
00036 
00037 using std::streambuf;
00038 
00039 bool preproc_define::operator==(preproc_define const &v) const {
00040     return value == v.value && arguments == v.arguments;
00041 }
00042 
00043 class preprocessor;
00044 class preprocessor_file;
00045 class preprocessor_data;
00046 class preprocessor_streambuf;
00047 struct preprocessor_deleter;
00048 
00049 class preprocessor
00050 {
00051     preprocessor *const old_preprocessor_;
00052     std::string old_textdomain_;
00053     std::string old_location_;
00054     int old_linenum_;
00055 protected:
00056     preprocessor_streambuf &target_;
00057     std::vector<std::string> *called_macros_;
00058     preprocessor(preprocessor_streambuf &, std::vector<std::string> *);
00059 public:
00060     virtual bool get_chunk() = 0;
00061     virtual ~preprocessor();
00062 };
00063 
00064 class preprocessor_streambuf: public streambuf
00065 {
00066     std::string out_buffer_;
00067     virtual int underflow();
00068     std::ostringstream buffer_;
00069     preprocessor *current_;
00070     preproc_map *defines_;
00071     preproc_map default_defines_;
00072     std::string textdomain_;
00073     std::string location_;
00074     std::string *error_log;
00075     int linenum_;
00076     int depth_;
00077     int buffer_size_;
00078     bool quoted_;
00079     friend class preprocessor;
00080     friend class preprocessor_file;
00081     friend class preprocessor_data;
00082     friend struct preprocessor_deleter;
00083     preprocessor_streambuf(preprocessor_streambuf const &);
00084 public:
00085     preprocessor_streambuf(preproc_map *, std::string *);
00086     std::string lineno_string(std::string const &);
00087     void error(const std::string &, const std::string &);
00088 };
00089 
00090 preprocessor_streambuf::preprocessor_streambuf(preproc_map *def, std::string *err_log) :
00091     streambuf(), 
00092     out_buffer_(""), 
00093     buffer_(), 
00094     current_(NULL), 
00095     defines_(def),
00096     default_defines_(),
00097     textdomain_(PACKAGE), 
00098     location_(""), 
00099     error_log(err_log),
00100     linenum_(0), 
00101     depth_(0),
00102     buffer_size_(0),
00103     quoted_(false)
00104 {
00105 }
00106 
00107 preprocessor_streambuf::preprocessor_streambuf(preprocessor_streambuf const &t) :
00108     streambuf(),
00109     out_buffer_(""),
00110     buffer_(),
00111     current_(NULL),
00112     defines_(t.defines_),
00113     default_defines_(),
00114     textdomain_(PACKAGE),
00115     location_(""),
00116     error_log(t.error_log),
00117     linenum_(0),
00118     depth_(t.depth_),
00119     buffer_size_(0), 
00120     quoted_(t.quoted_)
00121 {
00122 }
00123 
00124 // underflow is called when the internal buffer has been consumed 
00125 // so that more can be prepared
00126 int preprocessor_streambuf::underflow()
00127 {
00128     unsigned sz = 0;
00129     if (char *gp = gptr()) {
00130         if (gp < egptr()) {
00131             // Sanity check: the internal buffer has not been totally consumed,
00132             // should we force the caller to use what remains first?
00133             return *gp;
00134         }
00135         // The buffer has been completely read; fill it again.
00136         // Keep part of the previous buffer, to ensure putback capabilities.
00137         sz = out_buffer_.size();
00138         buffer_.str(std::string());
00139         if (sz > 3) {
00140             buffer_ << out_buffer_.substr(sz - 3);
00141             sz = 3;
00142         }
00143         else
00144         {
00145             buffer_ << out_buffer_;
00146         }
00147             buffer_size_ = sz;
00148     } else {
00149         // The internal get-data pointer is null
00150     }
00151     const int desired_fill_amount = 2000;
00152     while (current_ && buffer_size_ < desired_fill_amount) {
00153         // Process files and data chunks until the desired buffer size is reached
00154         if (!current_->get_chunk()) {
00155              // Delete the current preprocessor item to restore its predecessor
00156             delete current_;
00157         }
00158     }
00159     // Update the internal state and data pointers
00160     out_buffer_ = buffer_.str();
00161     char *begin = &*out_buffer_.begin();
00162     unsigned bs = out_buffer_.size();
00163     setg(begin, begin + sz, begin + bs);
00164     if (sz >= bs)
00165         return EOF;
00166     return static_cast<unsigned char>(*(begin + sz));
00167 }
00168 
00169 std::string preprocessor_streambuf::lineno_string(std::string const &lineno)
00170 {
00171     std::vector< std::string > pos = utils::quoted_split(lineno, ' ');
00172     std::vector< std::string >::const_iterator i = pos.begin(), end = pos.end();
00173     std::string included_from = " included from ";
00174     std::string res;
00175     while (i != end) {
00176         std::string const &line = *(i++);
00177         std::string const &file = i != end ? *(i++) : "<unknown>";
00178         if (!res.empty())
00179             res += included_from;
00180         res += file + ':' + line;
00181     }
00182     if (res.empty()) res = "???";
00183     return res;
00184 }
00185 
00186 void preprocessor_streambuf::error(const std::string& error_type, const std::string &pos)
00187 {
00188     utils::string_map i18n_symbols;
00189     std::string position, error;
00190     position = lineno_string(pos);
00191     error = error_type + " at " + position;
00192     ERR_CF << error << '\n';
00193     if(error_log!=NULL)
00194         *error_log += error + '\n';
00195 
00196     throw preproc_config::error(error);
00197 }
00198 
00199 
00200 /**
00201  * preprocessor
00202  * 
00203  * This is the base class for all input to be parsed by the preprocessor.
00204  * When initialized, it will inform the stream (target_) that it is the current scope.
00205  * When it reaches its end of its scope, the stream will delete it, 
00206  * and when it is deleted, it will manage the stream 
00207  * to cause the previous scope to resume.
00208  */
00209 preprocessor::preprocessor(preprocessor_streambuf &t, std::vector<std::string> *callstack) :
00210     old_preprocessor_(t.current_),
00211     old_textdomain_(t.textdomain_),
00212     old_location_(t.location_),
00213     old_linenum_(t.linenum_),
00214     target_(t),
00215     called_macros_(callstack)
00216 {
00217     ++target_.depth_;
00218     target_.current_ = this;
00219 }
00220 
00221 namespace {
00222 void count_extra_digits(int const& line_number, int& buffer_size) {
00223     for(int digit_mark = 10; line_number >= digit_mark; digit_mark *= 10) {
00224         // The number is larger than one digit, calculate additional string size
00225         ++buffer_size;
00226     }
00227 }
00228 } // end anonymous namespace
00229 
00230 preprocessor::~preprocessor()
00231 {
00232     assert(target_.current_ == this);
00233     target_.current_  = old_preprocessor_;
00234     target_.location_ = old_location_;
00235     target_.linenum_  = old_linenum_;
00236     target_.textdomain_ = old_textdomain_;
00237     if (!old_location_.empty()) {
00238         target_.buffer_ << "\376line " << old_linenum_ << ' ' << old_location_ << '\n';
00239         const int fixed_char_count = 9;
00240         count_extra_digits(old_linenum_, target_.buffer_size_);
00241         target_.buffer_size_ += old_location_.size() + fixed_char_count;
00242     }
00243     if (!old_textdomain_.empty()) {
00244         target_.buffer_ << "\376textdomain " << old_textdomain_ << '\n';
00245         const int fixed_char_count = 13;
00246         target_.buffer_size_ += old_textdomain_.size() + fixed_char_count;
00247     }
00248     --target_.depth_;
00249 }
00250 
00251 /**
00252  *  preprocessor_file
00253  *
00254  *  This represents a WML element that resolves to a directory or file inclusion, 
00255  *  such as '{themes/}'
00256  */
00257 class preprocessor_file: preprocessor
00258 {
00259     std::vector< std::string > files_;
00260     std::vector< std::string >::const_iterator pos_, end_;
00261 public:
00262     preprocessor_file(preprocessor_streambuf &, std::vector<std::string> *, std::string const &);
00263     virtual bool get_chunk();
00264 };
00265 
00266 class preprocessor_data: preprocessor
00267 {
00268     struct token_desc
00269     {
00270         //! @todo FIXME: add enum for token type, with explanation of the different types
00271         char type;
00272         int stack_pos;
00273         int linenum;
00274     };
00275     scoped_istream in_;
00276     std::string directory_;
00277     std::vector< std::string > strings_;
00278     std::vector< token_desc > tokens_;
00279     bool is_macro;
00280     int slowpath_, //! @todo FIXME: add explanation of this variable
00281         skipping_, //will get set to true when skipping text(e.g. #ifdef that evaluates to false)
00282         linenum_;
00283 
00284     std::string read_word();
00285     std::string read_line();
00286     void skip_spaces();
00287     void skip_eol();
00288     void push_token(char);
00289     void pop_token();
00290     void put(char);
00291     void put(std::string const & /*, int change_line 
00292     = 0 */);
00293 public:
00294     preprocessor_data(preprocessor_streambuf &, 
00295                       std::vector<std::string> *,
00296                       std::istream *,
00297                       std::string const &history,
00298                       std::string const &name, int line,
00299                       std::string const &dir, std::string const &domain,
00300                       std::string* = NULL);
00301     ~preprocessor_data();
00302     virtual bool get_chunk();
00303 };
00304 
00305 preprocessor_file::preprocessor_file(preprocessor_streambuf &t, std::vector<std::string> *callstack,
00306         std::string const &name) :
00307     preprocessor(t,callstack),
00308     files_(),
00309     pos_(),
00310     end_()
00311 {
00312     if (is_directory(name))
00313         get_files_in_dir(name, &files_, NULL, ENTIRE_FILE_PATH, SKIP_MEDIA_DIR, DO_REORDER);
00314     else {
00315         std::istream * file_stream = istream_file(name);
00316         if (!file_stream->good()) {
00317             ERR_CF << "Could not open file " << name << "\n";
00318             delete file_stream;
00319         }
00320         else
00321             new preprocessor_data(t, called_macros_, file_stream, "", name, 1, directory_name(name), t.textdomain_);
00322     }
00323     pos_ = files_.begin();
00324     end_ = files_.end();
00325 }
00326 
00327 /**
00328  * preprocessor_file::get_chunk()
00329  *
00330  * Inserts and processes the next file in the list of included files.
00331  * @return  false if there is no next file.
00332  */
00333 bool preprocessor_file::get_chunk()
00334 {
00335     while (pos_ != end_) {
00336         const std::string &name = *(pos_++);
00337         unsigned sz = name.size();
00338         // Use reverse iterator to optimize testing
00339         if (sz < 5 || !std::equal(name.rbegin(), name.rbegin() + 4, "gfc."))
00340             continue;
00341         new preprocessor_file(target_, called_macros_, name);
00342         return true;
00343     }
00344     return false;
00345 }
00346 
00347 preprocessor_data::preprocessor_data(preprocessor_streambuf &t, std::vector<std::string> *callstack, 
00348         std::istream *i, std::string const &history, std::string const &name, int linenum, 
00349         std::string const &directory, std::string const &domain, std::string *symbol) :
00350     preprocessor(t,callstack), 
00351     in_(i), 
00352     directory_(directory),
00353     strings_(),
00354     tokens_(),
00355     slowpath_(0), 
00356     skipping_(0), 
00357     linenum_(linenum)
00358 {
00359     if(symbol!=NULL)
00360     {
00361         is_macro=true;
00362         called_macros_->push_back(*symbol);
00363     }
00364     else
00365     {
00366         is_macro=false;
00367     }
00368     
00369     std::ostringstream s;
00370 
00371     s << history;
00372     if (!name.empty()) {
00373         std::string ename(name);
00374         if (!history.empty())
00375             s << ' ';
00376         s << utils::escape(ename, " \\");
00377     }
00378     if (!t.location_.empty())
00379         s << ' ' << t.linenum_ << ' ' << t.location_;
00380     t.location_ = s.str();
00381     t.linenum_ = linenum;
00382     t.textdomain_ = domain;
00383 
00384     t.buffer_ << "\376line " << linenum
00385         << ' ' << t.location_ << "\n\376textdomain " << domain << '\n';
00386     const int fixed_char_count = 22;
00387     count_extra_digits(linenum, t.buffer_size_);
00388     t.buffer_size_ += t.location_.size() + domain.size() + fixed_char_count;
00389 
00390     push_token('*');
00391 }
00392 
00393 preprocessor_data::~preprocessor_data()
00394 {
00395     if(is_macro)
00396     {
00397         called_macros_->pop_back();
00398     }
00399 }
00400 
00401 void preprocessor_data::push_token(char t)
00402 {
00403     token_desc token = { t, strings_.size(), linenum_ };
00404     tokens_.push_back(token);
00405     std::ostringstream s;
00406     if (!skipping_ && slowpath_) {
00407         s << "\376line " << linenum_ << ' ' << target_.location_
00408           << "\n\376textdomain " << target_.textdomain_ << '\n';
00409     }
00410     strings_.push_back(s.str());
00411 }
00412 
00413 void preprocessor_data::pop_token()
00414 {
00415     strings_.erase(strings_.begin() + tokens_.back().stack_pos, strings_.end());
00416     tokens_.pop_back();
00417 }
00418 
00419 void preprocessor_data::skip_spaces()
00420 {
00421     for(;;) {
00422         int c = in_->peek();
00423         if (!in_->good() || (c != ' ' && c != '\t'))
00424             return;
00425         in_->get();
00426     }
00427 }
00428 
00429 void preprocessor_data::skip_eol()
00430 {
00431     for(;;) {
00432         int c = in_->get();
00433         if (c == '\n') {
00434             ++linenum_;
00435             return;
00436         }
00437         if (!in_->good())
00438             return;
00439     }
00440 }
00441 
00442 std::string preprocessor_data::read_word()
00443 {
00444     std::string res;
00445     for(;;) {
00446         int c = in_->peek();
00447         if (c == preprocessor_streambuf::traits_type::eof() || utils::portable_isspace(c)) {
00448             // DBG_CF << "(" << res << ")\n";
00449             return res;
00450         }
00451         in_->get();
00452         res += static_cast<char>(c);
00453     }
00454 }
00455 
00456 std::string preprocessor_data::read_line()
00457 {
00458     std::string res;
00459     for(;;) {
00460         int c = in_->get();
00461         if (c == '\n') {
00462             ++linenum_;
00463             return res;
00464         }
00465         if (!in_->good())
00466             return res;
00467         if (c != '\r')
00468             res += static_cast<char>(c);
00469     }
00470 }
00471 
00472 void preprocessor_data::put(char c)
00473 {
00474     if (skipping_)
00475         return;
00476     if (slowpath_) {
00477         strings_.back() += c;
00478         return;
00479     }
00480     if ((linenum_ != target_.linenum_ && c != '\n') ||
00481         (linenum_ != target_.linenum_ + 1 && c == '\n')) {
00482         target_.linenum_ = linenum_;
00483         if (c == '\n')
00484             --target_.linenum_;
00485 
00486         target_.buffer_ << "\376line " << target_.linenum_
00487             << ' ' << target_.location_ << '\n';
00488         const int fixed_char_count = 9;
00489         count_extra_digits(target_.linenum_, target_.buffer_size_);
00490         target_.buffer_size_ += target_.location_.size() + fixed_char_count;
00491     }
00492     if (c == '\n')
00493         ++target_.linenum_;
00494     target_.buffer_ << c;
00495     target_.buffer_size_ += 1;
00496 }
00497 
00498 void preprocessor_data::put(std::string const &s /*, int line_change*/)
00499 {
00500     if (skipping_)
00501         return;
00502     if (slowpath_) {
00503         strings_.back() += s;
00504         return;
00505     }
00506     target_.buffer_ << s;
00507 //  target_.linenum_ += line_change;
00508     target_.buffer_size_ += s.size();
00509 }
00510 
00511 bool preprocessor_data::get_chunk()
00512 {
00513     char c = in_->get();
00514     token_desc &token = tokens_.back();
00515     if (!in_->good()) {
00516         // The end of file was reached. 
00517         // Make sure we don't have any incomplete tokens.
00518         char const *s;
00519         switch (token.type) {
00520         case '*': return false; // everything is fine
00521         case 'i':
00522         case 'I':
00523         case 'j':
00524         case 'J': s = "#ifdef or #ifndef"; break;
00525         case '"': s = "Quoted string"; break;
00526         case '[':
00527         case '{': s = "Macro substitution"; break;
00528         case '(': s = "Macro argument"; break;
00529         default: s = "???";
00530         }
00531         std::ostringstream error;
00532         error<<s<<" not terminated";        
00533         std::ostringstream location;
00534         location<<linenum_<<' '<<target_.location_;
00535         pop_token();
00536         target_.error(error.str(), location.str());
00537     }
00538     if (c == '\n')
00539         ++linenum_;
00540     if (c == '\376') {
00541         std::string buffer(1, c);
00542         for(;;) {
00543             char d = in_->get();
00544             if (!in_->good() || d == '\n')
00545                 break;
00546             buffer += d;
00547         }
00548         buffer += '\n';
00549         // line_change = 1-1 = 0
00550         put(buffer);
00551     } else if (c == '"') {
00552         if (token.type == '"') {
00553             target_.quoted_ = false;
00554             put(c);
00555             if (!skipping_ && slowpath_) {
00556                 std::string tmp = strings_.back();
00557                 pop_token();
00558                 strings_.back() += tmp;
00559             } else
00560                 pop_token();
00561         } else if (!target_.quoted_) {
00562             target_.quoted_ = true;
00563             if (token.type == '{')
00564                 token.type = '[';
00565             push_token('"');
00566             put(c);
00567         } else {
00568             std::string error="Nested quoted string";
00569             std::ostringstream location;
00570             location<<linenum_<<' '<<target_.location_;
00571             target_.error(error, location.str());
00572         }
00573     } else if (c == '{') {
00574         if (token.type == '{')
00575             token.type = '[';
00576         push_token('{');
00577         ++slowpath_;
00578     } else if (c == ')' && token.type == '(') {
00579         tokens_.pop_back();
00580         strings_.push_back(std::string());
00581     } else if (c == '#' && !target_.quoted_) {
00582         std::string command = read_word();
00583         bool comment = false;
00584         if (command == "define") {
00585             skip_spaces();
00586             int linenum = linenum_;
00587             std::vector< std::string > items = utils::split(read_line(), ' ');
00588             if (items.empty()) {
00589                 std::string error="No macro name found after #define directive";        
00590                 std::ostringstream location;
00591                 location<<linenum_<<' '<<target_.location_;
00592                 target_.error(error, location.str());
00593             }
00594             std::string symbol = items.front();
00595             items.erase(items.begin());
00596             int found_enddef = 0;
00597             std::string buffer;
00598             for(;;) {
00599                 if (!in_->good())
00600                     break;
00601                 char d = in_->get();
00602                 if (d == '\n')
00603                     ++linenum_;
00604                 buffer += d;
00605                 if (d == '#')
00606                     found_enddef = 1;
00607                 else if (found_enddef > 0)
00608                     if (++found_enddef == 7) {
00609                         if (std::equal(buffer.end() - 6, buffer.end(), "enddef"))
00610                             break;
00611                         else
00612                             found_enddef = 0;
00613                     }
00614             }
00615             if (found_enddef != 7) {
00616                 std::string error="Unterminated preprocessor definition at";        
00617                 std::ostringstream location;
00618                 location<<linenum_<<' '<<target_.location_;
00619                 target_.error(error, location.str());
00620             }
00621             if (!skipping_) {
00622                 buffer.erase(buffer.end() - 7, buffer.end());
00623                 target_.defines_->insert(std::make_pair(
00624                     symbol, preproc_define(buffer, items, target_.textdomain_,
00625                                            linenum + 1, target_.location_)));
00626                 LOG_CF << "defining macro " << symbol << " (location " << target_.location_ << ")\n";
00627             }
00628         } else if (command == "ifdef") {
00629             skip_spaces();
00630             std::string const &symbol = read_word();
00631             bool skip = target_.defines_->count(symbol) == 0;
00632             DBG_CF << "testing for macro " << symbol << ": " << (skip ? "not defined" : "defined") << '\n';
00633             if (skip)
00634                 ++skipping_;
00635             push_token(skip ? 'J' : 'i');
00636         } else if (command == "ifndef") {
00637             skip_spaces();
00638             std::string const &symbol = read_word();
00639             bool skip = target_.defines_->count(symbol) != 0;
00640             DBG_CF << "testing for macro " << symbol << ": " << (skip ? "not defined" : "defined") << '\n';
00641             if (skip)
00642                 ++skipping_;
00643             push_token(skip ? 'J' : 'i');
00644         } else if (command == "else") {
00645             if (token.type == 'J') {
00646                 pop_token();
00647                 --skipping_;
00648                 push_token('j');
00649             } else if (token.type == 'i') {
00650                 pop_token();
00651                 ++skipping_;
00652                 push_token('I');
00653             } else {
00654                 std::string error="Unexpected #else at";
00655                 std::ostringstream location;
00656                 location<<linenum_<<' '<<target_.location_;
00657                 target_.error(error, location.str());
00658             }
00659         } else if (command == "endif") {
00660             switch (token.type) {
00661             case 'I':
00662             case 'J': --skipping_;
00663             case 'i':
00664             case 'j': break;
00665             default:
00666                 std::string error="Unexpected #endif at";
00667                 std::ostringstream location;
00668                 location<<linenum_<<' '<<target_.location_;
00669                 target_.error(error, location.str());
00670             }
00671             pop_token();
00672         } else if (command == "textdomain") {
00673             skip_spaces();
00674             std::string const &s = read_word();
00675             put("#textdomain ");
00676             put(s);
00677             target_.textdomain_ = s;
00678             comment = true;
00679         } else if (command == "enddef") {
00680             std::string error="Unexpected #enddef at";
00681             std::ostringstream location;
00682             location<<linenum_<<' '<<target_.location_;
00683             target_.error(error, location.str());
00684         } else if (command == "undef") {
00685             skip_spaces();
00686             std::string const &symbol = read_word();
00687             target_.defines_->erase(symbol);
00688             LOG_CF << "undefine macro " << symbol << " (location " << target_.location_ << ")\n";
00689         } else
00690             comment = true;
00691         skip_eol();
00692         if (comment)
00693             put('\n');
00694     } else if (token.type == '{' || token.type == '[') {
00695         if (c == '(') {
00696             if (token.type == '[')
00697                 token.type = '{';
00698             else
00699                 strings_.pop_back();
00700             push_token('(');
00701         } else if (utils::portable_isspace(c)) {
00702             if (token.type == '[') {
00703                 strings_.push_back(std::string());
00704                 token.type = '{';
00705             }
00706         } else if (c == '}') {
00707             --slowpath_;
00708             if (skipping_) {
00709                 pop_token();
00710                 return true;
00711             }
00712             if (token.type == '{') {
00713                 if (!strings_.back().empty()) {
00714                     std::ostringstream error;
00715                         std::ostringstream location;
00716                     error << "Can't parse new macro parameter with a macro call scope open";
00717                     location<<linenum_<<' '<<target_.location_;
00718                     target_.error(error.str(), location.str());
00719                 }
00720                 strings_.pop_back();
00721             }
00722 
00723             std::string symbol = strings_[token.stack_pos];
00724             std::string::size_type pos;
00725             while ((pos = symbol.find('\376')) != std::string::npos) {
00726                 std::string::iterator b = symbol.begin(); // invalidated at each iteration
00727                 symbol.erase(b + pos, b + symbol.find('\n', pos + 1) + 1);
00728             }
00729             // If this is a known pre-processing symbol, then we insert it, 
00730             // otherwise we assume it's a file name to load.
00731             preproc_map::const_iterator macro = target_.defines_->find(symbol);
00732             if(macro != target_.defines_->end()) {          
00733 
00734 //! @todo it seems some addons use other names instead of include so disable the 
00735 //! code for now. Once 1.4 has been released we can try to fix it again or
00736 //! make it mandatory to use INCLUDE for this purpose.
00737 #if 0           
00738                 // INCLUDE is special and is allowed to be used recusively.
00739                 if(symbol != "INCLUDE") {
00740                     for(std::vector<std::string>::iterator 
00741                             iter=called_macros_->begin(); 
00742                             iter!=called_macros_->end(); ++iter) {
00743                         if(*iter==symbol) {
00744                             std::ostringstream error;
00745                             error << "symbol '" << symbol << "' will cause a recursive macro call";
00746                             std::ostringstream location;
00747                             location<<linenum_<<' '<<target_.location_;
00748                             target_.error(error.str(), location.str());
00749                         }
00750                     }
00751                 }
00752 #endif                  
00753                 preproc_define const &val = macro->second;
00754                 size_t nb_arg = strings_.size() - token.stack_pos - 1;
00755                 if (nb_arg != val.arguments.size()) {
00756                     std::ostringstream error;
00757                     error << "preprocessor symbol '" << symbol << "' expects "
00758                           << val.arguments.size() << " arguments, but has "
00759                           << nb_arg << " arguments";
00760                     std::ostringstream location;
00761                     location<<linenum_<<' '<<target_.location_;
00762                     target_.error(error.str(), location.str());
00763                 }
00764 
00765                 std::stringstream *buffer = new std::stringstream;
00766                 std::string::const_iterator i_bra = val.value.end();
00767                 int macro_num = val.linenum;
00768                 std::string macro_textdomain = val.textdomain;
00769                 bool quoting = false;
00770                 for(std::string::const_iterator i = val.value.begin(),
00771                     i_end = val.value.end(); i != i_end; ++i) {
00772                     char c = *i;
00773                     if (c == '\n') {
00774                         ++macro_num;
00775                     } else if (c == '"') {
00776                         quoting = !quoting;
00777                     }
00778 
00779                     if (c == '{') {
00780                         if (i_bra != i_end)
00781                             buffer->write(&*i_bra - 1, i - i_bra + 1);
00782                         i_bra = i + 1;
00783                     } else if (i_bra == i_end) {
00784                         if (c == '#' && !quoting) {
00785                             // Keep track of textdomain changes in the body of the
00786                             // macro, so they can be restored after each substitution
00787                             // of a macro argument.
00788                             std::string::const_iterator i_beg = i + 1;
00789                             if (i_end - i_beg >= 13 &&
00790                                 std::equal(i_beg, i_beg + 10, "textdomain")) {
00791                                 i_beg += 10;
00792                                 i = std::find(i_beg, i_end, '\n');
00793                                 if (i_beg != i)
00794                                     ++i_beg;
00795                                 macro_textdomain = std::string(i_beg, i);
00796                                 *buffer << "#textdomain " << macro_textdomain;
00797                                 ++macro_num;
00798                                 c = '\n';
00799                             } else if((i_end - i_beg < 6 || (!std::equal(i_beg, i_beg + 6, "define")
00800                             && !std::equal(i_beg, i_beg + 6, "ifndef")))
00801                             && (i_end - i_beg < 5 || (!std::equal(i_beg, i_beg + 5, "ifdef") 
00802                             && !std::equal(i_beg, i_beg + 5, "endif") && !std::equal(i_beg, i_beg + 5, "undef")))
00803                             && (i_end - i_beg < 4 || !std::equal(i_beg, i_beg + 4, "else"))) {
00804                                 // Check for define, ifdef, ifndef, endif, undef, else.
00805                                 // Otherwise, this is a comment and should be skipped.
00806                                 i = std::find(i_beg, i_end, '\n');
00807                                 ++macro_num;
00808                                 c = '\n';
00809                             }
00810                         }
00811                         buffer->put(c);
00812                     } else if (c == '}') {
00813                         size_t sz = i - i_bra;
00814                         for(size_t n = 0; n < nb_arg; ++n) {
00815                             std::string const &arg = val.arguments[n];
00816                             if (arg.size() != sz ||
00817                                 !std::equal(i_bra, i, arg.begin()))
00818                                 continue;
00819                             *buffer << strings_[token.stack_pos + n + 1]
00820                                     << "\376line " << macro_num
00821                                     << ' ' << val.location << "\n\376textdomain "
00822                                     << macro_textdomain << '\n';
00823                             i_bra = i_end;
00824                             break;
00825                         }
00826                         if (i_bra != i_end) {
00827                             // The bracketed text was no macro argument
00828                             buffer->write(&*i_bra - 1, sz + 2);
00829                             i_bra = i_end;
00830                         }
00831                     }
00832                 }
00833 
00834                 pop_token();
00835                 std::string const &dir = directory_name(val.location.substr(0, val.location.find(' ')));
00836                 if (!slowpath_) {
00837                     DBG_CF << "substituting macro " << symbol << '\n';
00838                     new preprocessor_data(target_, called_macros_, buffer, val.location, "",
00839                                           val.linenum, dir, val.textdomain, &symbol);
00840                 } else {
00841                     DBG_CF << "substituting (slow) macro " << symbol << '\n';
00842                     std::ostringstream res;
00843                     preprocessor_streambuf *buf =
00844                         new preprocessor_streambuf(target_);
00845                     {   std::istream in(buf);
00846                         new preprocessor_data(*buf, called_macros_, buffer, val.location, "",
00847                                               val.linenum, dir, val.textdomain, &symbol);
00848                         res << in.rdbuf(); }
00849                     delete buf;
00850                     strings_.back() += res.str();
00851                 }
00852             } else if (target_.depth_ < 40) {
00853                 LOG_CF << "Macro definition not found for " << symbol << " , attempting to open as file.\n";
00854                 pop_token();
00855                 std::string prefix;
00856                 std::string nfname;
00857                 std::string const &newfilename = symbol;
00858                 // If the filename begins with a '~', 
00859                 //  then look in the user's data directory. 
00860                 // If the filename begins with a '@', 
00861                 //  then we look in the user's data directory,
00862                 // but default to the standard data directory if it's not found there.
00863                 if(newfilename != "" && (newfilename[0] == '~' || newfilename[0] == '@')) {
00864                     nfname = newfilename;
00865                     nfname.erase(nfname.begin(),nfname.begin()+1);
00866                     nfname = get_user_data_dir() + "/data/" + nfname;
00867 
00868                     LOG_CF << "got relative name '" << newfilename
00869                         << "' -> '" << nfname << "'\n";
00870 
00871                     if(newfilename[0] == '@' && file_exists(nfname) == false
00872                        && is_directory(nfname) == false)
00873                     {
00874                         nfname = "data/" + newfilename.substr(1);
00875                     }
00876                 } else if(newfilename.size() >= 2 && newfilename[0] == '.'
00877                     && newfilename[1] == '/' )
00878                 {
00879                     // If the filename begins with a "./", 
00880                     // then look in the same directory as the file 
00881                     // currrently being preprocessed.
00882                     nfname = newfilename;
00883                     nfname.erase(nfname.begin(),nfname.begin()+2);
00884                     nfname = directory_ + nfname;
00885                 } else {
00886                     nfname = "data/" + newfilename;
00887                 }
00888 
00889                 // Ignore filenames that start with '../' or contain '/../'.
00890                 if (newfilename.rfind("../", 0) == std::string::npos
00891                     && newfilename.find("/../") == std::string::npos)
00892                 {
00893                     if (!slowpath_)
00894                         new preprocessor_file(target_, called_macros_, nfname);
00895                     else {
00896                         std::ostringstream res;
00897                         preprocessor_streambuf *buf =
00898                             new preprocessor_streambuf(target_);
00899                         {   std::istream in(buf);
00900                             new preprocessor_file(*buf, called_macros_, nfname);
00901                             res << in.rdbuf(); }
00902                         delete buf;
00903                         strings_.back() += res.str();
00904                     }
00905                 } else {
00906                     ERR_CF << "Illegal path '" << newfilename
00907                         << "' found (../ not allowed).\n";
00908                 }
00909             } else {
00910                 ERR_CF << "Too much nested preprocessing inclusions at "
00911                        << linenum_ << ' ' << target_.location_
00912                        << ". Aborting.\n";
00913                 pop_token();
00914             }
00915         } else {
00916             strings_.back() += c;
00917             token.type = '[';
00918         }
00919     } else
00920         put(c);
00921     return true;
00922 }
00923 
00924 struct preprocessor_deleter: std::basic_istream<char>
00925 {
00926     preprocessor_streambuf *buf_;
00927     preproc_map *defines_;
00928     std::vector<std::string> *callstack_;
00929     std::string *error_log;
00930     preprocessor_deleter(preprocessor_streambuf *buf, preproc_map *defines, std::vector<std::string>*);
00931     ~preprocessor_deleter();
00932 };
00933 
00934 preprocessor_deleter::preprocessor_deleter(preprocessor_streambuf *buf, 
00935         preproc_map *defines, 
00936         std::vector<std::string> *callstack)
00937     : std::basic_istream<char>(buf), buf_(buf), defines_(defines), callstack_(callstack), error_log(buf->error_log)
00938 {
00939 }
00940 
00941 preprocessor_deleter::~preprocessor_deleter()
00942 {
00943     rdbuf(NULL);
00944     delete buf_;
00945     delete defines_;
00946     delete callstack_;
00947 }
00948 
00949 
00950 std::istream *preprocess_file(std::string const &fname,
00951                               preproc_map *defines, std::string *error_log)
00952 {
00953     log_scope("preprocessing file...");
00954     preproc_map *owned_defines = NULL;
00955     if (!defines) {
00956         // If no preproc_map has been given, create a new one, 
00957         // and ensure it is destroyed when the stream is 
00958 // ??
00959         // by giving it to the deleter.
00960         owned_defines = new preproc_map;
00961         defines = owned_defines;
00962     }
00963     preprocessor_streambuf *buf = new preprocessor_streambuf(defines, error_log);
00964     std::vector<std::string> *callstack=new std::vector<std::string>;
00965     new preprocessor_file(*buf, callstack, fname);
00966     if(error_log!=NULL&&error_log->empty()==false)
00967         throw preproc_config::error("Error preprocessing files.");
00968     return new preprocessor_deleter(buf, owned_defines,callstack);
00969 }
00970 

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs