The Battle for Wesnoth  1.19.5+dev
preprocessor.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2024
3  by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4  Copyright (C) 2003 by David White <dave@whitevine.net>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 /**
18  * @file
19  * WML preprocessor.
20  */
21 
23 
24 #include "buffered_istream.hpp"
25 #include "config.hpp"
26 #include "log.hpp"
28 #include "serialization/parser.hpp"
30 #include "game_version.hpp"
31 #include "wesconfig.h"
32 #include "deprecation.hpp"
33 
34 #include <stdexcept>
35 #include <deque>
36 
37 static lg::log_domain log_preprocessor("preprocessor");
38 #define ERR_PREPROC LOG_STREAM(err, log_preprocessor)
39 #define WRN_PREPROC LOG_STREAM(warn, log_preprocessor)
40 #define LOG_PREPROC LOG_STREAM(info, log_preprocessor)
41 #define DBG_PREPROC LOG_STREAM(debug, log_preprocessor)
42 
43 static const std::string current_file_str = "CURRENT_FILE";
44 static const std::string current_dir_str = "CURRENT_DIRECTORY";
45 static const std::string left_curly_str = "LEFT_BRACE";
46 static const std::string right_curly_str = "RIGHT_BRACE";
47 
48 // map associating each filename encountered to a number
49 static std::map<std::string, int> file_number_map;
50 
51 static bool encode_filename = true;
52 
53 static std::string preprocessor_error_detail_prefix = "\n ";
54 
55 // get filename associated to this code
56 static std::string get_filename(const std::string& file_code)
57 {
58  if(!encode_filename) {
59  return file_code;
60  }
61 
62  std::stringstream s;
63  s << file_code;
64  int n = 0;
65  s >> std::hex >> n;
66 
67  for(const auto& p : file_number_map) {
68  if(p.second == n) {
69  return p.first;
70  }
71  }
72 
73  return "<unknown>";
74 }
75 
76 // Get code associated to this filename
77 static std::string get_file_code(const std::string& filename)
78 {
79  if(!encode_filename) {
80  return filename;
81  }
82 
83  // Current number of encountered filenames
84  static int current_file_number = 0;
85 
86  int& fnum = file_number_map[utils::escape(filename, " \\")];
87  if(fnum == 0) {
88  fnum = ++current_file_number;
89  }
90 
91  std::ostringstream shex;
92  shex << std::hex << fnum;
93 
94  return shex.str();
95 }
96 
97 // decode the filenames placed in a location
98 static std::string get_location(const std::string& loc)
99 {
100  std::string res;
101  std::vector<std::string> pos = utils::quoted_split(loc, ' ');
102 
103  if(pos.empty()) {
104  return res;
105  }
106 
107  std::vector<std::string>::const_iterator i = pos.begin(), end = pos.end();
108  while(true) {
109  res += get_filename(*(i++));
110 
111  if(i == end) {
112  break;
113  }
114 
115  res += ' ';
116  res += *(i++);
117 
118  if(i == end) {
119  break;
120  }
121 
122  res += ' ';
123  }
124 
125  return res;
126 }
127 
128 
129 // ==================================================================================
130 // PREPROC_DEFINE IMPLEMENTATION
131 // ==================================================================================
132 
134 {
135  return value == v.value && arguments == v.arguments;
136 }
137 
139 {
140  if(location < v.location) {
141  return true;
142  }
143 
144  if(v.location < location) {
145  return false;
146  }
147 
148  return linenum < v.linenum;
149 }
150 
151 void preproc_define::write_argument(config_writer& writer, const std::string& arg) const
152 {
153  const std::string key = "argument";
154 
155  writer.open_child(key);
156 
157  writer.write_key_val("name", arg);
158  writer.close_child(key);
159 }
160 
161 void preproc_define::write_argument(config_writer& writer, const std::string& arg, const std::string& default_value) const
162 {
163  const std::string key = "argument";
164 
165  writer.open_child(key);
166 
167  writer.write_key_val("name", arg);
168  writer.write_key_val("default", default_value);
169  writer.close_child(key);
170 }
171 
172 void preproc_define::write(config_writer& writer, const std::string& name) const
173 {
174  const std::string key = "preproc_define";
175  writer.open_child(key);
176 
177  writer.write_key_val("name", name);
178  writer.write_key_val("value", value);
179  writer.write_key_val("textdomain", textdomain);
180  writer.write_key_val("linenum", std::to_string(linenum));
181  writer.write_key_val("location", get_location(location));
182 
183  if(is_deprecated()) {
184  writer.open_child("deprecated");
185  writer.write_key_val("level", int(*deprecation_level));
186  writer.write_key_val("version", deprecation_version.str());
187  writer.write_key_val("message", deprecation_message);
188  writer.close_child("deprecated");
189  }
190 
191  for(const std::string& arg : arguments) {
192  write_argument(writer, arg);
193  }
194 
195  for(const auto& [key, default_value] : optional_arguments) {
196  write_argument(writer, key, default_value);
197  }
198 
199  writer.close_child(key);
200 }
201 
203 {
204  if(cfg.has_attribute("default")) {
205  optional_arguments.emplace(cfg["name"], cfg["default"]);
206  } else {
207  arguments.push_back(cfg["name"]);
208  }
209 }
210 
211 void preproc_define::read(const config& cfg)
212 {
213  value = cfg["value"].str();
214  textdomain = cfg["textdomain"].str();
215  linenum = cfg["linenum"].to_int();
216  location = cfg["location"].str();
217 
218  if(auto deprecated = cfg.optional_child("deprecated")) {
219  deprecation_level = DEP_LEVEL(deprecated.value()["level"].to_int());
220  deprecation_version = deprecated.value()["version"].str();
221  deprecation_message = deprecated.value()["message"].str();
222  }
223 
224  for(const config& arg : cfg.child_range("argument")) {
225  read_argument(arg);
226  }
227 }
228 
229 preproc_map::value_type preproc_define::read_pair(const config& cfg)
230 {
231  preproc_define second;
232  second.read(cfg);
233 
234  return preproc_map::value_type(cfg["name"], second);
235 }
236 
237 std::ostream& operator<<(std::ostream& stream, const preproc_define& def)
238 {
239  return stream << "value: " << def.value << " arguments: " << def.location;
240 }
241 
242 std::ostream& operator<<(std::ostream& stream, const preproc_map::value_type& def)
243 {
244  return stream << def.second;
245 }
246 
247 // ==================================================================================
248 // PREPROCESSOR BASE
249 // ==================================================================================
250 
252 
253 /**
254  * Base class for preprocessing an input.
255  */
257 {
259 
260 protected:
261  /**
262  * Sets up a new preprocessor for stream buffer \a t.
263  * Saves the current preprocessing context of #parent_. It will be automatically restored on destruction.
264  *
265  * It relies on preprocessor_streambuf so it's implemented after that class is declared.
266  */
268 
270 
271 public:
272  virtual ~preprocessor()
273  {
274  }
275 
276  /** Allows specifying any actions that need to be called after the constructor completes. */
277  virtual void init()
278  {
279  }
280 
281  /**
282  * Preprocesses and sends some text to the #parent_ buffer.
283  * @return false when the input has no data left.
284  */
285  virtual bool get_chunk() = 0;
286 
288 
289  /** Returns the appropriate parsing mode for this preprocessor. */
290  virtual MODE parse_mode()
291  {
292  return NO_PARSING;
293  }
294 
295 private:
296  std::string old_textdomain_;
297  std::string old_location_;
298 
300 };
301 
302 
303 // ==================================================================================
304 // PREPROCESSOR BUFFER
305 // ==================================================================================
306 
307 /**
308  * Target for sending preprocessed output.
309  * Objects of this class can be plugged into an STL stream.
310  */
311 class preprocessor_streambuf : public std::streambuf
312 {
313 public:
315  : std::streambuf()
316  , out_buffer_("")
317  , buffer_()
319  , defines_(def)
320  , default_defines_()
322  , location_("")
323  , linenum_(0)
324  , quoted_(false)
325  {
326  }
327 
328  /** Decodes the filenames placed in a location. */
329  std::string get_current_file();
330 
331  void error(const std::string&, int);
332  void warning(const std::string&, int);
333 
334  template<typename T, typename... A>
335  void add_preprocessor(A&&... args)
336  {
337  preprocessor_queue_.emplace_back(new T(*this, std::forward<A>(args)...));
338  preprocessor_queue_.back()->init();
339  }
340 
342  {
343  preprocessor_queue_.pop_back();
344  }
345 
346  int depth() const
347  {
348  return preprocessor_queue_.size();
349  }
350 
352  {
353  return preprocessor_queue_.empty() ? nullptr : preprocessor_queue_.back().get();
354  }
355 
356 private:
358  : std::streambuf()
359  , out_buffer_("")
360  , buffer_()
362  , defines_(t.defines_)
363  , default_defines_()
365  , location_("")
366  , linenum_(0)
367  , quoted_(t.quoted_)
368  {
369  }
370 
371  /** Inherited from basic_streambuf. */
372  virtual int underflow() override;
373 
375 
376  /** Buffer read by the STL stream. */
377  std::string out_buffer_;
378 
379  /** Buffer filled by the _current_ preprocessor. */
380  std::stringstream buffer_;
381 
382  /** Input preprocessor queue. */
383  std::deque<std::unique_ptr<preprocessor>> preprocessor_queue_;
384 
387 
388  std::string textdomain_;
389  std::string location_;
390 
391  int linenum_;
392 
393  /**
394  * Set to true if one preprocessor for this target started to read a string.
395  * Deeper-nested preprocessors are then forbidden to.
396  */
397  bool quoted_;
398 
399  friend class preprocessor;
400  friend class preprocessor_file;
401  friend class preprocessor_data;
403 };
404 
405 /** Preprocessor constructor. */
407  : parent_(t)
408  , old_textdomain_(t.textdomain_)
409  , old_location_(t.location_)
410  , old_linenum_(t.linenum_)
411 {
412 }
413 
414 /**
415  * Called by an STL stream whenever it has reached the end of #out_buffer_.
416  * Fills #buffer_ by calling the _current_ preprocessor, then copies its
417  * content into #out_buffer_.
418  * @return the first character of #out_buffer_ if any, EOF otherwise.
419  */
421 {
422  unsigned sz = 0;
423  if(char* gp = gptr()) {
424  if(gp < egptr()) {
425  // Sanity check: the internal buffer has not been totally consumed,
426  // should we force the caller to use what remains first?
427  return *gp;
428  }
429 
430  // The buffer has been completely read; fill it again.
431  // Keep part of the previous buffer, to ensure putback capabilities.
432  sz = out_buffer_.size();
433  buffer_.str(std::string());
434 
435  if(sz > 3) {
436  buffer_ << out_buffer_.substr(sz - 3);
437  sz = 3;
438  } else {
439  buffer_ << out_buffer_;
440  }
441  } else {
442  // The internal get-data pointer is null
443  }
444 
445  const int desired_fill_amount = 2000;
446 
447  while(current() && buffer_.rdbuf()->in_avail() < desired_fill_amount) {
448  // Process files and data chunks until the desired buffer size is reached
449  if(!current()->get_chunk()) {
450  // Drop the current preprocessor item from the queue.
452  }
453  }
454 
455  // Update the internal state and data pointers
456  out_buffer_ = buffer_.str();
457  if(out_buffer_.empty()) {
458  return EOF;
459  }
460 
461  char* begin = &*out_buffer_.begin();
462  unsigned bs = out_buffer_.size();
463 
464  setg(begin, begin + sz, begin + bs);
465 
466  if(sz >= bs) {
467  return EOF;
468  }
469 
470  return static_cast<unsigned char>(*(begin + sz));
471 }
472 
473 /**
474 * Restores the old preprocessing context.
475 * Appends location and domain directives to the buffer, so that the parser
476 * notices these changes.
477 */
479 {
480  preprocessor* current = this->current();
481 
482  if(!current->old_location_.empty()) {
484  }
485 
487  buffer_ << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << current->old_textdomain_ << '\n';
488  }
489 
493 
494  // Drop the preprocessor from the queue.
496 }
497 
499 {
500  unsigned nested_level = 0;
501 
502  preprocessor* pre = nullptr;
503 
504  // Iterate backwards over queue to get the last non-macro preprocessor.
505  for(auto p = preprocessor_queue_.rbegin(); p != preprocessor_queue_.rend(); ++p) {
506  pre = p->get();
507 
508  if(!pre || pre->parse_mode() == preprocessor::PARSES_FILE) {
509  break;
510  }
511 
512  if(pre->parse_mode() == preprocessor::PARSES_MACRO) {
513  ++nested_level;
514  }
515  }
516 
517  std::string res;
518  std::vector<std::string> pos = utils::quoted_split(location_, ' ');
519 
520  if(pos.size() <= 2 * nested_level) {
521  return res;
522  }
523 
524  return get_filename(pos[2 * nested_level]);
525 }
526 
527 std::string lineno_string(const std::string& lineno)
528 {
529  std::vector<std::string> pos = utils::quoted_split(lineno, ' ');
530  std::vector<std::string>::const_iterator i = pos.begin(), end = pos.end();
531  std::string included_from = preprocessor_error_detail_prefix + "included from ";
532  std::string res;
533 
534  while(i != end) {
535  const std::string& line = *(i++);
536 
537  if(!res.empty()) {
538  res += included_from;
539  }
540 
541  if(i != end) {
542  res += get_filename(*(i++));
543  } else {
544  res += "<unknown>";
545  }
546 
547  res += ':' + line;
548  }
549 
550  if(res.empty()) {
551  res = "???";
552  }
553 
554  return res;
555 }
556 
557 void preprocessor_streambuf::error(const std::string& error_type, int l)
558 {
559  std::string position, error;
560  std::ostringstream pos;
561 
562  pos << l << ' ' << location_;
563  position = lineno_string(pos.str());
564 
565  error = error_type + '\n';
566  error += "at " + position;
567 
568  ERR_PREPROC << error;
569 
571 }
572 
573 void preprocessor_streambuf::warning(const std::string& warning_type, int l)
574 {
575  std::string position, warning;
576  std::ostringstream pos;
577 
578  pos << l << ' ' << location_;
579  position = lineno_string(pos.str());
580 
581  warning = warning_type + '\n';
582  warning += "at " + position;
583 
584  WRN_PREPROC << warning;
585 }
586 
587 
588 // ==================================================================================
589 // PREPROCESSOR FILE
590 // ==================================================================================
591 
592 /**
593  * Specialized preprocessor for handling a file or a set of files.
594  * A preprocessor_file object is created when a preprocessor encounters an
595  * inclusion directive that resolves to a file or directory, e.g. '{themes/}'.
596  */
598 {
599 public:
600  /** Constructor. It relies on preprocessor_data so it's implemented after that class is declared. */
601  preprocessor_file(preprocessor_streambuf& t, const std::string& name, std::size_t symbol_index = -1);
602 
603  virtual void init() override;
604 
605  /**
606  * Inserts and processes the next file in the list of included files.
607  * @return false if there is no next file.
608  */
609  virtual bool get_chunk() override
610  {
611  while(pos_ != end_) {
612  const std::string& name = *(pos_++);
613  unsigned sz = name.size();
614 
615  // Use reverse iterator to optimize testing
616  if(sz < 5 || !std::equal(name.rbegin(), name.rbegin() + 4, "gfc.")) {
617  continue;
618  }
619 
621  return true;
622  }
623 
624  return false;
625  }
626 
627 private:
628  std::vector<std::string> files_;
629  std::vector<std::string>::const_iterator pos_, end_;
630 
631  const std::string& name_;
632 
634 };
635 
636 
637 // ==================================================================================
638 // PREPROCESSOR DATA
639 // ==================================================================================
640 
641 /**
642  * Specialized preprocessor for handling any kind of input stream.
643  * This is the core of the preprocessor.
644  */
646 {
647  /** Description of a preprocessing chunk. */
648  struct token_desc
649  {
650  enum class token_type {
651  start, // Toplevel
652  process_if, // Processing the "if" branch of a ifdef/ifndef (the "else" branch will be skipped)
653  process_else, // Processing the "else" branch of a ifdef/ifndef
654  skip_if, // Skipping the "if" branch of a ifdef/ifndef (the "else" branch, if any, will be processed)
655  skip_else, // Skipping the "else" branch of a ifdef/ifndef
656  string, // Processing a string
657  verbatim, // Processing a verbatim string
658  macro_space, // Processing between chunks of a macro call (skip spaces)
659  macro_chunk, // Processing inside a chunk of a macro call (stop on space or '(')
660  macro_parens // Processing a parenthesized macro argument
661  };
662 
663  token_desc(token_type type, const int stack_pos, const int linenum)
664  : type(type)
666  , linenum(linenum)
667  {
668  }
669 
671 
672  /** Starting position in #strings_ of the delayed text for this chunk. */
674  int linenum;
675  };
676 
677  /**
678  * Manages the lifetime of the @c std::istream pointer we own.
679  *
680  * Since @ref in_ uses the stream as well this object must be created
681  * before @ref in_ and destroyed after @ref in_ is destroyed.
682  */
684 
685  /** Input stream. */
687 
688  std::string directory_;
689 
690  /** Buffer for delayed input processing. */
691  std::vector<std::string> strings_;
692 
693  /** Mapping of macro arguments to their content. */
694  std::unique_ptr<std::map<std::string, std::string>> local_defines_;
695 
696  /** Stack of nested preprocessing chunks. */
697  std::vector<token_desc> tokens_;
698 
699  /**
700  * Set to true whenever input tokens cannot be directly sent to the target
701  * buffer. For instance, this happens with macro arguments. In that case,
702  * the output is redirected toward #strings_ until it can be processed.
703  */
705 
706  /**
707  * Non-zero when the preprocessor has to skip some input text.
708  * Increased whenever entering a conditional branch that is not useful,
709  * e.g. a ifdef that evaluates to false.
710  */
712  int linenum_;
713 
714  /** True iff we are currently parsing a macros content, otherwise false. */
716 
717  std::string read_word();
718  std::string read_line();
719  std::string read_rest_of_line();
720 
721  void skip_spaces();
722  void skip_eol();
724  void pop_token();
725  void put(char);
726  void put(const std::string& /*, int change_line = 0 */);
727  void conditional_skip(bool skip);
728 
729 public:
732  const std::string& history,
733  const std::string& name,
734  int line,
735  const std::string& dir,
736  const std::string& domain,
737  std::unique_ptr<std::map<std::string, std::string>> defines,
738  bool is_define = false);
739 
740  virtual bool get_chunk() override;
741 
742  virtual preprocessor::MODE parse_mode() override
743  {
745  }
746 
751 };
752 
754 {
755  throw std::logic_error("don't compare tokens with characters");
756 }
757 
759 {
760  return rhs == lhs;
761 }
762 
764 {
765  return !(lhs == rhs);
766 }
767 
769 {
770  return rhs != lhs;
771 }
772 
773 /** preprocessor_file constructor. */
774 preprocessor_file::preprocessor_file(preprocessor_streambuf& t, const std::string& name, std::size_t symbol_index)
775  : preprocessor(t)
776  , files_()
777  , pos_()
778  , end_()
779  , name_(name)
780  , is_directory_(filesystem::is_directory(name))
781 {
782  if(is_directory_) {
783  filesystem::get_files_in_dir(name, &files_, nullptr,
787  );
788 
789  for(const std::string& fname : files_) {
790  std::size_t cpos = fname.rfind(" ");
791 
792  if(cpos != std::string::npos && cpos >= symbol_index) {
793  std::stringstream ss;
794  ss << "Found filename containing whitespace: '" << filesystem::base_name(fname)
795  << "' in included directory '" << name << "'.\nThe included symbol probably looks similar to '"
796  << filesystem::directory_name(fname.substr(symbol_index)) << "'";
797 
798  // TODO: find a real linenumber
799  parent_.error(ss.str(), -1);
800  }
801  }
802  } else {
803  // Handled in the init() function.
804  }
805 
806  pos_ = files_.begin();
807  end_ = files_.end();
808 }
809 
811 {
812  if(is_directory_) {
813  return;
814  }
815 
817 
818  if(!file_stream->good()) {
819  ERR_PREPROC << "Could not open file " << name_;
820  } else {
821  parent_.add_preprocessor<preprocessor_data>(std::move(file_stream), "",
824  );
825  }
826 }
827 
830  const std::string& history,
831  const std::string& name,
832  int linenum,
833  const std::string& directory,
834  const std::string& domain,
835  std::unique_ptr<std::map<std::string, std::string>> defines,
836  bool is_define)
837  : preprocessor(t)
838  , in_scope_(std::move(i))
839  , in_(*in_scope_)
840  , directory_(directory)
841  , strings_()
842  , local_defines_(std::move(defines))
843  , tokens_()
844  , slowpath_(0)
845  , skipping_(0)
846  , linenum_(linenum)
847  , is_define_(is_define)
848 {
849  std::ostringstream s;
850  s << history;
851 
852  if(!name.empty()) {
853  if(!history.empty()) {
854  s << ' ';
855  }
856 
857  s << get_file_code(name);
858  }
859 
860  if(!t.location_.empty()) {
861  s << ' ' << t.linenum_ << ' ' << t.location_;
862  }
863 
864  t.location_ = s.str();
865  t.linenum_ = linenum;
866 
867  t.buffer_ << INLINED_PREPROCESS_DIRECTIVE_CHAR << "line " << linenum << ' ' << t.location_ << '\n';
868 
869  if(t.textdomain_ != domain) {
870  t.buffer_ << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << domain << '\n';
871  t.textdomain_ = domain;
872  }
873 
875 }
876 
878 {
879  tokens_.emplace_back(t, strings_.size(), linenum_);
880 
882  // Macro expansions do not have any associated storage at start.
883  return;
885  /* Quoted strings are always inlined in the parent token. So
886  * they need neither storage nor metadata, unless the parent
887  * token is a macro expansion.
888  */
889  token_desc::token_type& outer_type = tokens_[tokens_.size() - 2].type;
890  if(outer_type != token_desc::token_type::macro_space) {
891  return;
892  }
893 
895  tokens_.back().stack_pos = strings_.size() + 1;
896  }
897 
898  std::ostringstream s;
899  if(!skipping_ && slowpath_) {
900  s << INLINED_PREPROCESS_DIRECTIVE_CHAR << "line " << linenum_ << ' ' << parent_.location_ << "\n"
901  << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << parent_.textdomain_ << '\n';
902  }
903 
904  strings_.push_back(s.str());
905 }
906 
908 {
909  token_desc::token_type inner_type = tokens_.back().type;
910  unsigned stack_pos = tokens_.back().stack_pos;
911 
912  tokens_.pop_back();
913 
914  token_desc::token_type& outer_type = tokens_.back().type;
915 
916  if(inner_type == token_desc::token_type::macro_parens) {
917  // Parenthesized macro arguments are left on the stack.
918  assert(outer_type == token_desc::token_type::macro_space);
919  return;
920  }
921 
922  if(inner_type == token_desc::token_type::string || inner_type == token_desc::token_type::verbatim) {
923  // Quoted strings are always inlined.
924  assert(stack_pos == strings_.size());
925  return;
926  }
927 
928  if(outer_type == token_desc::token_type::macro_space) {
929  /* A macro expansion does not have any associated storage.
930  * Instead, storage of the inner token is not discarded
931  * but kept as a new macro argument. But if the inner token
932  * was a macro expansion, it is about to be appended, so
933  * prepare for it.
934  */
936  strings_.erase(strings_.begin() + stack_pos, strings_.end());
937  strings_.emplace_back();
938  }
939 
940  assert(stack_pos + 1 == strings_.size());
942 
943  return;
944  }
945 
946  strings_.erase(strings_.begin() + stack_pos, strings_.end());
947 }
948 
950 {
951  while(true) {
952  int c = in_.peek();
953 
954  if(in_.eof() || (c != ' ' && c != '\t')) {
955  return;
956  }
957 
958  in_.get();
959  }
960 }
961 
963 {
964  while(true) {
965  int c = in_.get();
966 
967  if(c == '\n') {
968  ++linenum_;
969  return;
970  }
971 
972  if(in_.eof()) {
973  return;
974  }
975  }
976 }
977 
979 {
980  std::string res;
981 
982  while(true) {
983  int c = in_.peek();
984 
985  if(c == preprocessor_streambuf::traits_type::eof() || utils::portable_isspace(c)) {
986  // DBG_PREPROC << "(" << res << ")";
987  return res;
988  }
989 
990  in_.get();
991  res += static_cast<char>(c);
992  }
993 }
994 
996 {
997  std::string res;
998 
999  while(true) {
1000  int c = in_.get();
1001 
1002  if(c == '\n') {
1003  ++linenum_;
1004  return res;
1005  }
1006 
1007  if(in_.eof()) {
1008  return res;
1009  }
1010 
1011  if(c != '\r') {
1012  res += static_cast<char>(c);
1013  }
1014  }
1015 }
1016 
1018 {
1019  std::string res;
1020 
1021  while(in_.peek() != '\n' && !in_.eof()) {
1022  int c = in_.get();
1023 
1024  if(c != '\r') {
1025  res += static_cast<char>(c);
1026  }
1027  }
1028 
1029  return res;
1030 }
1031 
1033 {
1034  if(skipping_) {
1035  return;
1036  }
1037 
1038  if(slowpath_) {
1039  strings_.back() += c;
1040  return;
1041  }
1042 
1043  int cond_linenum = c == '\n' ? linenum_ - 1 : linenum_;
1044 
1045  if(unsigned diff = cond_linenum - parent_.linenum_) {
1046  parent_.linenum_ = cond_linenum;
1047 
1048  if(diff <= parent_.location_.size() + 11) {
1049  parent_.buffer_ << std::string(diff, '\n');
1050  } else {
1052  }
1053  }
1054 
1055  if(c == '\n') {
1056  ++parent_.linenum_;
1057  }
1058 
1059  parent_.buffer_ << c;
1060 }
1061 
1062 void preprocessor_data::put(const std::string& s /*, int line_change*/)
1063 {
1064  if(skipping_) {
1065  return;
1066  }
1067 
1068  if(slowpath_) {
1069  strings_.back() += s;
1070  return;
1071  }
1072 
1073  parent_.buffer_ << s;
1074  // parent_.linenum_ += line_change;
1075 }
1076 
1078 {
1079  if(skip) {
1080  ++skipping_;
1081  }
1082 
1084 }
1085 
1087 {
1088  char c = static_cast<char>(in_.get());
1089  token_desc& token = tokens_.back();
1090 
1091  if(in_.eof()) {
1092  // The end of file was reached.
1093  // Make sure we don't have any incomplete tokens.
1094  char const* s;
1095 
1096  switch(token.type) {
1098  return false; // everything is fine
1103  s = "#ifdef or #ifndef";
1104  break;
1106  s = "Quoted string";
1107  break;
1109  s = "Verbatim string";
1110  break;
1113  s = "Macro substitution";
1114  break;
1116  s = "Macro argument";
1117  break;
1118  default:
1119  s = "???";
1120  }
1121 
1122  parent_.error(std::string(s) + " not terminated", token.linenum);
1123  }
1124 
1125  if(c == '\n') {
1126  ++linenum_;
1127  }
1128 
1129  if(c == static_cast<char>(INLINED_PREPROCESS_DIRECTIVE_CHAR)) {
1130  std::string buffer(1, c);
1131 
1132  while(true) {
1133  char d = static_cast<char>(in_.get());
1134 
1135  if(in_.eof() || d == '\n') {
1136  break;
1137  }
1138 
1139  buffer += d;
1140  }
1141 
1142  buffer += '\n';
1143  // line_change = 1-1 = 0
1144  put(buffer);
1146  put(c);
1147 
1148  if(c == '>' && in_.peek() == '>') {
1149  put(in_.get());
1150  pop_token();
1151  }
1152  } else if(c == '<' && in_.peek() == '<') {
1153  in_.get();
1155  put('<');
1156  put('<');
1157  } else if(c == '"') {
1159  parent_.quoted_ = false;
1160  put(c);
1161  pop_token();
1162  } else if(!parent_.quoted_) {
1163  parent_.quoted_ = true;
1165  put(c);
1166  } else {
1167  parent_.error("Nested quoted string", linenum_);
1168  }
1169  } else if(c == '{') {
1171  ++slowpath_;
1172  } else if(c == ')' && token.type == token_desc::token_type::macro_parens) {
1173  pop_token();
1174  } else if(c == '#' && !parent_.quoted_) {
1175  std::string command = read_word();
1176  bool comment = false;
1177 
1178  if(command == "define") {
1179  skip_spaces();
1180  int linenum = linenum_;
1181  std::vector<std::string> items = utils::split(read_line(), ' ');
1182  std::map<std::string, std::string> optargs;
1183 
1184  if(items.empty()) {
1185  parent_.error("No macro name found after #define directive", linenum);
1186  }
1187 
1188  std::string symbol = items.front();
1189  items.erase(items.begin());
1190  int found_arg = 0, found_enddef = 0, found_deprecate = 0;
1191  utils::optional<DEP_LEVEL> deprecation_level;
1192  std::string buffer, deprecation_detail;
1193  version_info deprecation_version = game_config::wesnoth_version;
1194  while(true) {
1195  if(in_.eof())
1196  break;
1197  char d = static_cast<char>(in_.get());
1198  if(d == '\n')
1199  ++linenum_;
1200  buffer += d;
1201  if(d == '#') {
1202  if(in_.peek() == 'a') {
1203  found_arg = 1;
1204  } else if(in_.peek() == 'd') {
1205  found_deprecate = 1;
1206  } else {
1207  found_enddef = 1;
1208  }
1209  } else {
1210  if(found_arg > 0 && ++found_arg == 4) {
1211  if(std::equal(buffer.end() - 3, buffer.end(), "arg")) {
1212  buffer.erase(buffer.end() - 4, buffer.end());
1213 
1214  skip_spaces();
1215  std::string argname = read_word();
1216  skip_eol();
1217 
1218  std::string argbuffer;
1219 
1220  int found_endarg = 0;
1221  while(true) {
1222  if(in_.eof()) {
1223  break;
1224  }
1225 
1226  char e = static_cast<char>(in_.get());
1227  if(e == '\n') {
1228  ++linenum_;
1229  }
1230 
1231  argbuffer += e;
1232 
1233  if(e == '#') {
1234  found_endarg = 1;
1235  } else if(found_endarg > 0 && ++found_endarg == 7) {
1236  if(std::equal(argbuffer.end() - 6, argbuffer.end(), "endarg")) {
1237  argbuffer.erase(argbuffer.end() - 7, argbuffer.end());
1238  optargs[argname] = argbuffer;
1239  skip_eol();
1240  break;
1241  } else {
1242  parent_.error("Unterminated #arg definition", linenum_);
1243  }
1244  }
1245  }
1246  }
1247  }
1248 
1249  if(found_deprecate > 0 && ++found_deprecate == 11) {
1250  if(std::equal(buffer.end() - 10, buffer.end(), "deprecated")) {
1251  buffer.erase(buffer.end() - 11, buffer.end());
1252  skip_spaces();
1253  try {
1254  DEP_LEVEL level = DEP_LEVEL(std::stoi(read_word()));
1255  if(deprecation_level) {
1256  deprecation_level = std::max(*deprecation_level, level);
1257  } else {
1258  deprecation_level = level;
1259  }
1260  } catch(const std::invalid_argument&) {
1261  // Meh, fall back to default of PREEMPTIVE...
1262  deprecation_level = DEP_LEVEL::PREEMPTIVE;
1263  }
1264  deprecation_version = game_config::wesnoth_version;
1265  if(deprecation_level == DEP_LEVEL::PREEMPTIVE || deprecation_level == DEP_LEVEL::FOR_REMOVAL) {
1266  skip_spaces();
1267  deprecation_version = std::max(deprecation_version, version_info(read_word()));
1268  }
1269  skip_spaces();
1270  if(!deprecation_detail.empty()){
1271  deprecation_detail += '\n';
1272  }
1273  deprecation_detail += read_rest_of_line();
1274  skip_eol();
1275  }
1276  }
1277 
1278  if(found_enddef > 0 && ++found_enddef == 7) {
1279  if(std::equal(buffer.end() - 6, buffer.end(), "enddef")) {
1280  break;
1281  } else {
1282  found_enddef = 0;
1283  if(std::equal(buffer.end() - 6, buffer.end(), "define")) { // TODO: Maybe add support for
1284  // this? This would fill feature
1285  // request #21343
1286  parent_.error(
1287  "Preprocessor error: #define is not allowed inside a #define/#enddef pair",
1288  linenum);
1289  }
1290  }
1291  }
1292  }
1293  }
1294 
1295  if(found_enddef != 7) {
1296  parent_.error("Unterminated preprocessor definition", linenum_);
1297  }
1298 
1299  if(!skipping_) {
1300  preproc_map::const_iterator old_i = parent_.defines_->find(symbol);
1301  if(old_i != parent_.defines_->end()) {
1302  std::ostringstream new_pos, old_pos;
1303  const preproc_define& old_d = old_i->second;
1304 
1305  new_pos << linenum << ' ' << parent_.location_;
1306  old_pos << old_d.linenum << ' ' << old_d.location;
1307 
1308  WRN_PREPROC << "Redefining macro " << symbol << " without explicit #undef at "
1309  << lineno_string(new_pos.str()) << '\n'
1310  << "previously defined at " << lineno_string(old_pos.str());
1311  }
1312 
1313  buffer.erase(buffer.end() - 7, buffer.end());
1314  (*parent_.defines_)[symbol]
1315  = preproc_define(buffer, items, optargs, parent_.textdomain_, linenum, parent_.location_,
1316  deprecation_detail, deprecation_level, deprecation_version);
1317 
1318  LOG_PREPROC << "defining macro " << symbol << " (location " << get_location(parent_.location_) << ")";
1319  }
1320  } else if(command == "ifdef" || command == "ifndef") {
1321  const bool negate = command[2] == 'n';
1322  skip_spaces();
1323  const std::string& symbol = read_word();
1324  if(symbol.empty()) {
1325  parent_.error("No macro argument found after #ifdef/#ifndef directive", linenum_);
1326  }
1327  bool found = parent_.defines_->count(symbol) != 0;
1328  DBG_PREPROC << "testing for macro " << symbol << ": " << (found ? "defined" : "not defined");
1329  conditional_skip(negate ? found : !found);
1330  } else if(command == "ifhave" || command == "ifnhave") {
1331  const bool negate = command[2] == 'n';
1332  skip_spaces();
1333  const std::string& symbol = read_word();
1334  if(symbol.empty()) {
1335  parent_.error("No path argument found after #ifhave/#ifnhave directive", linenum_);
1336  }
1337  bool found = filesystem::get_wml_location(symbol, directory_).has_value();
1338  DBG_PREPROC << "testing for file or directory " << symbol << ": " << (found ? "found" : "not found");
1339  conditional_skip(negate ? found : !found);
1340  } else if(command == "ifver" || command == "ifnver") {
1341  const bool negate = command[2] == 'n';
1342 
1343  skip_spaces();
1344  const std::string& vsymstr = read_word();
1345  skip_spaces();
1346  const std::string& vopstr = read_word();
1347  skip_spaces();
1348  const std::string& vverstr = read_word();
1349 
1350  const VERSION_COMP_OP vop = parse_version_op(vopstr);
1351 
1352  if(vop == OP_INVALID) {
1353  parent_.error("Invalid #ifver/#ifnver operator", linenum_);
1354  } else if(parent_.defines_->count(vsymstr) != 0) {
1355  const preproc_define& sym = (*parent_.defines_)[vsymstr];
1356 
1357  if(!sym.arguments.empty()) {
1358  parent_.error("First argument macro in #ifver/#ifnver should not require arguments", linenum_);
1359  }
1360 
1361  version_info const version1(sym.value);
1362  version_info const version2(vverstr);
1363 
1364  const bool found = do_version_check(version1, vop, version2);
1365  DBG_PREPROC << "testing version '" << version1.str() << "' against '" << version2.str() << "' ("
1366  << vopstr << "): " << (found ? "match" : "no match");
1367 
1368  conditional_skip(negate ? found : !found);
1369  } else {
1370  std::string err = "Undefined macro in #ifver/#ifnver first argument: '";
1371  err += vsymstr;
1372  err += "'";
1374  }
1375  } else if(command == "else") {
1377  pop_token();
1378  --skipping_;
1381  pop_token();
1382  ++skipping_;
1384  } else {
1385  parent_.error("Unexpected #else", linenum_);
1386  }
1387  } else if(command == "endif") {
1388  switch(token.type) {
1391  --skipping_;
1394  break;
1395  default:
1396  parent_.error("Unexpected #endif", linenum_);
1397  }
1398  pop_token();
1399  } else if(command == "textdomain") {
1400  skip_spaces();
1401  const std::string& s = read_word();
1402  if(s != parent_.textdomain_) {
1403  put("#textdomain ");
1404  put(s);
1405  parent_.textdomain_ = s;
1406  }
1407  comment = true;
1408  } else if(command == "enddef") {
1409  parent_.error("Unexpected #enddef", linenum_);
1410  } else if(command == "undef") {
1411  skip_spaces();
1412  const std::string& symbol = read_word();
1413  if(!skipping_) {
1414  parent_.defines_->erase(symbol);
1415  LOG_PREPROC << "undefine macro " << symbol << " (location " << get_location(parent_.location_) << ")";
1416  }
1417  } else if(command == "error") {
1418  if(!skipping_) {
1419  skip_spaces();
1420  std::ostringstream error;
1421  error << "#error: \"" << read_rest_of_line() << '"';
1422  parent_.error(error.str(), linenum_);
1423  } else
1424  DBG_PREPROC << "Skipped an error";
1425  } else if(command == "warning") {
1426  if(!skipping_) {
1427  skip_spaces();
1428  std::ostringstream warning;
1429  warning << "#warning: \"" << read_rest_of_line() << '"';
1430  parent_.warning(warning.str(), linenum_);
1431  } else {
1432  DBG_PREPROC << "Skipped a warning";
1433  }
1434  } else if(command == "deprecated") {
1435  // The current file is deprecated, so print a message
1436  skip_spaces();
1438  try {
1439  level = DEP_LEVEL(std::stoi(read_word()));
1440  } catch(const std::invalid_argument&) {
1441  // Meh, just fall back to the default of PREEMPTIVE...
1442  }
1445  skip_spaces();
1446  version = version_info(read_word());
1447  }
1448  skip_spaces();
1449  std::string detail = read_rest_of_line();
1451  } else {
1453  }
1454 
1455  skip_eol();
1456  if(comment) {
1457  put('\n');
1458  }
1460  if(c == '(') {
1461  // If a macro argument was started, it is implicitly ended.
1464  } else if(utils::portable_isspace(c)) {
1465  // If a macro argument was started, it is implicitly ended.
1467  } else if(c == '}') {
1468  --slowpath_;
1469  if(skipping_) {
1470  pop_token();
1471  return true;
1472  }
1473 
1474  // FIXME: is this obsolete?
1475  // if (token.type == token_desc::MACRO_SPACE) {
1476  // if (!strings_.back().empty()) {
1477  // std::ostringstream error;
1478  // std::ostringstream location;
1479  // error << "Can't parse new macro parameter with a macro call scope open";
1480  // location<<linenum_<<' '<<parent_.location_;
1481  // parent_.error(error.str(), location.str());
1482  // }
1483  // strings_.pop_back();
1484  //}
1485 
1486  if(strings_.size() <= static_cast<std::size_t>(token.stack_pos)) {
1487  parent_.error("No macro or file substitution target specified", linenum_);
1488  }
1489 
1490  std::string symbol = strings_[token.stack_pos];
1491  std::string::size_type pos;
1492  while((pos = symbol.find(INLINED_PREPROCESS_DIRECTIVE_CHAR)) != std::string::npos) {
1493  std::string::iterator b = symbol.begin(); // invalidated at each iteration
1494  symbol.erase(b + pos, b + symbol.find('\n', pos + 1) + 1);
1495  }
1496 
1497  std::map<std::string, std::string>::const_iterator arg;
1498  preproc_map::const_iterator macro;
1499 
1500  // If this is a known pre-processing symbol, then we insert it,
1501  // otherwise we assume it's a file name to load.
1502  if(symbol == current_file_str && strings_.size() - token.stack_pos == 1) {
1503  pop_token();
1505  } else if(symbol == current_dir_str && strings_.size() - token.stack_pos == 1) {
1506  pop_token();
1508  } else if(symbol == left_curly_str && strings_.size() - token.stack_pos == 1) {
1509  pop_token();
1510  put("{");
1511  } else if(symbol == right_curly_str && strings_.size() - token.stack_pos == 1) {
1512  pop_token();
1513  put("}");
1514  } else if(local_defines_ && (arg = local_defines_->find(symbol)) != local_defines_->end()) {
1515  if(strings_.size() - token.stack_pos != 1) {
1516  std::ostringstream error;
1517  error << "Macro argument '" << symbol << "' does not expect any arguments";
1518  parent_.error(error.str(), linenum_);
1519  }
1520 
1521  std::ostringstream v;
1522  v << arg->second << INLINED_PREPROCESS_DIRECTIVE_CHAR << "line " << linenum_ << ' ' << parent_.location_ << "\n"
1523  << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << parent_.textdomain_ << '\n';
1524 
1525  pop_token();
1526  put(v.str());
1527  } else if(parent_.depth() < 100 && (macro = parent_.defines_->find(symbol)) != parent_.defines_->end()) {
1528  const preproc_define& val = macro->second;
1529  std::size_t nb_arg = strings_.size() - token.stack_pos - 1;
1530  std::size_t optional_arg_num = 0;
1531 
1532  auto defines = std::make_unique<std::map<std::string, std::string>>();
1533  const std::string& dir = filesystem::directory_name(val.location.substr(0, val.location.find(' ')));
1534 
1535  if(val.is_deprecated()) {
1537  }
1538 
1539  for(std::size_t i = 0; i < nb_arg; ++i) {
1540  if(i < val.arguments.size()) {
1541  // Normal mandatory arguments
1542 
1543  (*defines)[val.arguments[i]] = strings_[token.stack_pos + i + 1];
1544  } else {
1545  // These should be optional argument overrides
1546 
1547  std::string str = strings_[token.stack_pos + i + 1];
1548  std::size_t equals_pos = str.find_first_of("=");
1549 
1550  if(equals_pos != std::string::npos) {
1551  std::size_t argname_pos = str.substr(0, equals_pos).find_last_of(" \n") + 1;
1552 
1553  std::string argname = str.substr(argname_pos, equals_pos - argname_pos);
1554 
1555  if(val.optional_arguments.find(argname) != val.optional_arguments.end()) {
1556  (*defines)[argname] = str.substr(equals_pos + 1);
1557 
1558  optional_arg_num++;
1559 
1560  DBG_PREPROC << "Found override for " << argname << " in call to macro " << symbol;
1561  } else {
1562  std::ostringstream warning;
1563  warning << "Unrecognized optional argument passed to macro '" << symbol << "': '"
1564  << argname << "'";
1565  parent_.warning(warning.str(), linenum_);
1566 
1567  optional_arg_num++; // To prevent the argument number check from blowing up
1568  }
1569  }
1570  }
1571  }
1572 
1573  // If the macro definition has any optional arguments, insert their defaults
1574  if(val.optional_arguments.size() > 0) {
1575  for(const auto& argument : val.optional_arguments) {
1576  if(defines->find(argument.first) == defines->end()) {
1577  std::unique_ptr<preprocessor_streambuf> buf(new preprocessor_streambuf(parent_));
1578 
1579  buf->textdomain_ = parent_.textdomain_;
1580  std::istream in(buf.get());
1581 
1582  filesystem::scoped_istream buffer{new std::istringstream(argument.second)};
1583 
1584  auto temp_defines = std::make_unique<std::map<std::string, std::string>>();
1585  temp_defines->insert(defines->begin(), defines->end());
1586 
1587  buf->add_preprocessor<preprocessor_data>(
1588  std::move(buffer), val.location, "", val.linenum, dir, val.textdomain, std::move(temp_defines), false);
1589 
1590  std::ostringstream res;
1591  res << in.rdbuf();
1592 
1593  DBG_PREPROC << "Setting default for optional argument " << argument.first << " in macro "
1594  << symbol;
1595 
1596  (*defines)[argument.first] = res.str();
1597  }
1598  }
1599  }
1600 
1601  if(nb_arg - optional_arg_num != val.arguments.size()) {
1602  const std::vector<std::string>& locations = utils::quoted_split(val.location, ' ');
1603  const std::string filename = locations.empty() ? "<command-line>" : get_filename(locations[0]);
1604  std::ostringstream error;
1605  error << "Preprocessor symbol '" << symbol << "' defined at " << filename << ":"
1606  << val.linenum << " expects " << val.arguments.size() << " arguments, but has "
1607  << nb_arg - optional_arg_num << " arguments";
1608  parent_.error(error.str(), linenum_);
1609  }
1610 
1611  filesystem::scoped_istream buffer{new std::istringstream(val.value)};
1612 
1613  pop_token();
1614 
1615  if(!slowpath_) {
1616  DBG_PREPROC << "substituting macro " << symbol;
1617 
1619  std::move(buffer), val.location, "", val.linenum, dir, val.textdomain, std::move(defines), true);
1620  } else {
1621  DBG_PREPROC << "substituting (slow) macro " << symbol;
1622 
1623  std::unique_ptr<preprocessor_streambuf> buf(new preprocessor_streambuf(parent_));
1624 
1625  // Make the nested preprocessor_data responsible for
1626  // restoring our current textdomain if needed.
1627  buf->textdomain_ = parent_.textdomain_;
1628 
1629  std::ostringstream res;
1630  {
1631  std::istream in(buf.get());
1632  buf->add_preprocessor<preprocessor_data>(
1633  std::move(buffer), val.location, "", val.linenum, dir, val.textdomain, std::move(defines), true);
1634 
1635  res << in.rdbuf();
1636  }
1637 
1638  put(res.str());
1639  }
1640  } else if(parent_.depth() < 40) {
1641  LOG_PREPROC << "Macro definition not found for " << symbol << ", attempting to open as file.";
1642  pop_token();
1643 
1644  if(auto nfname = filesystem::get_wml_location(symbol, directory_)) {
1645  if(!slowpath_)
1646  // nfname.size() - symbol.size() gives you an index into nfname
1647  // This does not necessarily match the symbol though, as it can start with ~ or ./
1648  parent_.add_preprocessor<preprocessor_file>(nfname.value(), nfname->size() - symbol.size());
1649  else {
1650  std::unique_ptr<preprocessor_streambuf> buf(new preprocessor_streambuf(parent_));
1651 
1652  std::ostringstream res;
1653  {
1654  std::istream in(buf.get());
1655  buf->add_preprocessor<preprocessor_file>(nfname.value(), nfname->size() - symbol.size());
1656 
1657  res << in.rdbuf();
1658  }
1659 
1660  put(res.str());
1661  }
1662  } else {
1663  std::ostringstream error;
1664  error << "Macro/file '" << symbol << "' is missing";
1665  parent_.error(error.str(), linenum_);
1666  }
1667  } else {
1668  parent_.error("Too many nested preprocessing inclusions", linenum_);
1669  }
1670  } else if(!skipping_) {
1672  std::ostringstream s;
1673  s << INLINED_PREPROCESS_DIRECTIVE_CHAR << "line " << linenum_ << ' ' << parent_.location_ << "\n"
1674  << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << parent_.textdomain_ << '\n';
1675 
1676  strings_.push_back(s.str());
1678  }
1679  put(c);
1680  }
1681  } else {
1682  put(c);
1683  }
1684 
1685  return true;
1686 }
1687 
1688 
1689 // ==================================================================================
1690 // PREPROCESSOR SCOPE HELPER
1691 // ==================================================================================
1692 
1693 struct preprocessor_scope_helper : std::basic_istream<char>
1694 {
1695  preprocessor_scope_helper(const std::string& fname, preproc_map* defines)
1696  : std::basic_istream<char>(nullptr)
1697  , buf_(nullptr)
1698  , local_defines_(nullptr)
1699  {
1700  //
1701  // If no defines were provided, we create a new local preproc_map and assign
1702  // it to defines temporarily. In this case, the map will be deleted once this
1703  // object is destroyed and defines will still be subsequently null.
1704  //
1705  if(!defines) {
1706  local_defines_.reset(new preproc_map);
1707  defines = local_defines_.get();
1708  }
1709 
1710  buf_.reset(new preprocessor_streambuf(defines));
1711 
1712  // Begin processing.
1713  buf_->add_preprocessor<preprocessor_file>(fname);
1714 
1715  //
1716  // TODO: not sure if this call is needed. Previously, this call was passed a
1717  // preprocessor_streambuf pointer and the std::basic_istream constructor was
1718  // called with its contents. However, at that point the preprocessing should
1719  // already have completed, meaning this call might be redundant. Not sure.
1720  //
1721  // - vultraz, 2017-08-31
1722  //
1723  init(buf_.get());
1724  }
1725 
1727  {
1728  clear(std::ios_base::goodbit);
1729  exceptions(std::ios_base::goodbit);
1730  rdbuf(nullptr);
1731  }
1732 
1733  std::unique_ptr<preprocessor_streambuf> buf_;
1734  std::unique_ptr<preproc_map> local_defines_;
1735 };
1736 
1737 
1738 // ==================================================================================
1739 // FREE-STANDING FUNCTIONS
1740 // ==================================================================================
1741 
1742 filesystem::scoped_istream preprocess_file(const std::string& fname, preproc_map* defines)
1743 {
1744  log_scope("preprocessing file " + fname + " ...");
1745 
1746  // NOTE: the preprocessor_scope_helper does *not* take ownership of defines.
1747  return filesystem::scoped_istream(new preprocessor_scope_helper(fname, defines));
1748 }
1749 
1750 void preprocess_resource(const std::string& res_name,
1751  preproc_map* defines_map,
1752  bool write_cfg,
1753  bool write_plain_cfg,
1754  const std::string& parent_directory)
1755 {
1756  if(filesystem::is_directory(res_name)) {
1757  std::vector<std::string> dirs, files;
1758 
1761 
1762  // Subdirectories
1763  for(const std::string& dir : dirs) {
1764  LOG_PREPROC << "processing sub-dir: " << dir;
1765  preprocess_resource(dir, defines_map, write_cfg, write_plain_cfg, parent_directory);
1766  }
1767 
1768  // Files in current directory
1769  for(const std::string& file : files) {
1770  if(filesystem::is_cfg(file)) {
1771  preprocess_resource(file, defines_map, write_cfg, write_plain_cfg, parent_directory);
1772  }
1773  }
1774 
1775  return;
1776  }
1777 
1778  LOG_PREPROC << "processing resource: " << res_name;
1779 
1780  // disable filename encoding to get clear #line in cfg.plain
1781  encode_filename = false;
1782 
1783  filesystem::scoped_istream stream = preprocess_file(res_name, defines_map);
1784 
1785  std::stringstream ss;
1786 
1787  // Set the failbit so if we get any preprocessor exceptions (e.g.:preproc_config::error)
1788  // they will be propagated in the main program, instead of just setting the
1789  // failbit on the stream. This was necessary in order for the MSVC and GCC
1790  // binaries to behave the same way.
1791  ss.exceptions(std::ios_base::failbit);
1792 
1793  ss << (*stream).rdbuf();
1794 
1795  LOG_PREPROC << "processing finished";
1796 
1797  if(write_cfg || write_plain_cfg) {
1798  config cfg;
1799  std::string streamContent = ss.str();
1800 
1801  read(cfg, streamContent);
1802 
1803  const std::string preproc_res_name = parent_directory + "/" + filesystem::base_name(res_name);
1804 
1805  // Write the processed cfg file
1806  if(write_cfg) {
1807  LOG_PREPROC << "writing cfg file: " << preproc_res_name;
1808 
1810  filesystem::scoped_ostream outStream(filesystem::ostream_file(preproc_res_name));
1811 
1812  write(*outStream, cfg);
1813  }
1814 
1815  // Write the plain cfg file
1816  if(write_plain_cfg) {
1817  LOG_PREPROC << "writing plain cfg file: " << (preproc_res_name + ".plain");
1818 
1820  filesystem::write_file(preproc_res_name + ".plain", streamContent);
1821  }
1822  }
1823 }
double t
Definition: astarsearch.cpp:63
Helper class for buffering a std::istream.
Helper class for buffering a std::istream.
bool eof() const
Is the end of input reached?
int get()
Gets and consumes a character from the buffer.
int peek()
Gets a character from the buffer.
Class for writing a config out to a file in pieces.
void close_child(const std::string &key)
void write_key_val(const std::string &key, const T &value)
This template function will work with any type that can be assigned to an attribute_value.
void open_child(const std::string &key)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
child_itors child_range(config_key_type key)
Definition: config.cpp:272
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:384
Specialized preprocessor for handling any kind of input stream.
std::unique_ptr< std::map< std::string, std::string > > local_defines_
Mapping of macro arguments to their content.
int skipping_
Non-zero when the preprocessor has to skip some input text.
virtual preprocessor::MODE parse_mode() override
Returns the appropriate parsing mode for this preprocessor.
void conditional_skip(bool skip)
int slowpath_
Set to true whenever input tokens cannot be directly sent to the target buffer.
std::string read_rest_of_line()
friend bool operator!=(preprocessor_data::token_desc::token_type, char)
std::vector< std::string > strings_
Buffer for delayed input processing.
filesystem::scoped_istream in_scope_
Manages the lifetime of the std::istream pointer we own.
preprocessor_data(preprocessor_streambuf &, filesystem::scoped_istream, const std::string &history, const std::string &name, int line, const std::string &dir, const std::string &domain, std::unique_ptr< std::map< std::string, std::string >> defines, bool is_define=false)
buffered_istream in_
Input stream.
friend bool operator==(preprocessor_data::token_desc::token_type, char)
bool is_define_
True iff we are currently parsing a macros content, otherwise false.
std::vector< token_desc > tokens_
Stack of nested preprocessing chunks.
std::string read_line()
std::string read_word()
virtual bool get_chunk() override
Preprocesses and sends some text to the parent_ buffer.
std::string directory_
void push_token(token_desc::token_type)
Specialized preprocessor for handling a file or a set of files.
std::vector< std::string >::const_iterator pos_
std::vector< std::string > files_
virtual bool get_chunk() override
Inserts and processes the next file in the list of included files.
const std::string & name_
std::vector< std::string >::const_iterator end_
virtual void init() override
Allows specifying any actions that need to be called after the constructor completes.
preprocessor_file(preprocessor_streambuf &t, const std::string &name, std::size_t symbol_index=-1)
Constructor.
Target for sending preprocessed output.
std::stringstream buffer_
Buffer filled by the current preprocessor.
preprocessor * current() const
bool quoted_
Set to true if one preprocessor for this target started to read a string.
preprocessor_streambuf(preproc_map *def)
preproc_map default_defines_
virtual int underflow() override
Inherited from basic_streambuf.
void restore_old_preprocessor()
Restores the old preprocessing context.
std::string out_buffer_
Buffer read by the STL stream.
std::string get_current_file()
Decodes the filenames placed in a location.
std::deque< std::unique_ptr< preprocessor > > preprocessor_queue_
Input preprocessor queue.
void add_preprocessor(A &&... args)
void error(const std::string &, int)
preprocessor_streambuf(const preprocessor_streambuf &t)
void warning(const std::string &, int)
Base class for preprocessing an input.
virtual MODE parse_mode()
Returns the appropriate parsing mode for this preprocessor.
std::string old_textdomain_
virtual void init()
Allows specifying any actions that need to be called after the constructor completes.
virtual ~preprocessor()
std::string old_location_
friend class preprocessor_streambuf
preprocessor(preprocessor_streambuf &t)
Sets up a new preprocessor for stream buffer t.
virtual bool get_chunk()=0
Preprocesses and sends some text to the parent_ buffer.
preprocessor_streambuf & parent_
Represents version numbers.
std::string str() const
Serializes the version number into string form.
Definitions for the interface to Wesnoth Markup Language (WML).
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
DEP_LEVEL
See https://wiki.wesnoth.org/CompatibilityStandards for more info.
Definition: deprecation.hpp:21
std::size_t i
Definition: function.cpp:1028
unsigned in
If equal to search_counter, the node is off the list.
bool do_version_check(const version_info &a, VERSION_COMP_OP op, const version_info &b)
VERSION_COMP_OP parse_version_op(const std::string &op_str)
Interfaces for manipulating version numbers of engine, add-ons, etc.
VERSION_COMP_OP
@ OP_INVALID
Standard logging facilities (interface).
#define log_scope(description)
Definition: log.hpp:278
void clear()
Clear the current render target.
Definition: draw.cpp:40
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:180
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:444
bool is_cfg(const std::string &filename)
Returns true if the file ends with the wmlfile extension.
std::string base_name(const std::string &file, const bool remove_extension)
Returns the base filename of a file, with directory name stripped.
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
static bool create_directory_if_missing_recursive(const bfs::path &dirpath)
Definition: filesystem.cpp:383
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:53
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
std::string get_short_wml_path(const std::string &filename)
Returns a short path to filename, skipping the (user) data directory.
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:54
const version_info wesnoth_version(VERSION)
logger & err()
Definition: log.cpp:307
struct utils::detail::formula_initer init
std::vector< std::string > quoted_split(const std::string &val, char c, int flags, char quote)
This function is identical to split(), except it does not split when it otherwise would if the previo...
bool portable_isspace(const char c)
std::string escape(const std::string &str, const char *special_chars)
Prepends a configurable set of characters with a backslash.
std::vector< std::string > split(const config_attribute_value &val)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
#define DBG_PREPROC
static lg::log_domain log_preprocessor("preprocessor")
static const std::string left_curly_str
static const std::string current_dir_str
#define ERR_PREPROC
std::string lineno_string(const std::string &lineno)
void preprocess_resource(const std::string &res_name, preproc_map *defines_map, bool write_cfg, bool write_plain_cfg, const std::string &parent_directory)
#define WRN_PREPROC
static std::string get_file_code(const std::string &filename)
static bool encode_filename
static const std::string current_file_str
bool operator==(preprocessor_data::token_desc::token_type, char)
static std::string get_filename(const std::string &file_code)
static std::string get_location(const std::string &loc)
std::ostream & operator<<(std::ostream &stream, const preproc_define &def)
#define LOG_PREPROC
static std::map< std::string, int > file_number_map
static const std::string right_curly_str
bool operator!=(preprocessor_data::token_desc::token_type rhs, char lhs)
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
static std::string preprocessor_error_detail_prefix
std::map< std::string, struct preproc_define > preproc_map
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:622
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:759
constexpr unsigned char INLINED_PREPROCESS_DIRECTIVE_CHAR
Definition: tokenizer.hpp:29
std::string filename
Filename.
void write(config_writer &, const std::string &) const
std::string value
version_info deprecation_version
bool operator<(const preproc_define &) const
std::string textdomain
void write_argument(config_writer &, const std::string &) const
void read(const config &)
void read_argument(const config &)
std::string deprecation_message
std::vector< std::string > arguments
std::map< std::string, std::string > optional_arguments
bool operator==(const preproc_define &) const
utils::optional< DEP_LEVEL > deprecation_level
static preproc_map::value_type read_pair(const config &)
std::string location
bool is_deprecated() const
Description of a preprocessing chunk.
token_desc(token_type type, const int stack_pos, const int linenum)
int stack_pos
Starting position in strings_ of the delayed text for this chunk.
std::unique_ptr< preprocessor_streambuf > buf_
std::unique_ptr< preproc_map > local_defines_
preprocessor_scope_helper(const std::string &fname, preproc_map *defines)
contains the current text being parsed as well as the token_type of what's being parsed.
Definition: tokenizer.hpp:41
token_type type
Definition: tokenizer.hpp:88
mock_char c
mock_party p
static map_location::direction n
static map_location::direction s
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
#define PACKAGE
Definition: wesconfig.h:23
#define d
#define e
#define b