The Battle for Wesnoth  1.19.23+dev
preprocessor.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2025
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 
36 static lg::log_domain log_preprocessor("preprocessor");
37 #define ERR_PREPROC LOG_STREAM(err, log_preprocessor)
38 #define WRN_PREPROC LOG_STREAM(warn, log_preprocessor)
39 #define LOG_PREPROC LOG_STREAM(info, log_preprocessor)
40 #define DBG_PREPROC LOG_STREAM(debug, log_preprocessor)
41 
42 static const std::string current_file_str = "CURRENT_FILE";
43 static const std::string current_dir_str = "CURRENT_DIRECTORY";
44 static const std::string left_curly_str = "LEFT_BRACE";
45 static const std::string right_curly_str = "RIGHT_BRACE";
46 
47 // map associating each filename encountered to a number
48 static std::map<std::string, int> file_number_map;
49 
50 static bool encode_filename = true;
51 
52 static std::string preprocessor_error_detail_prefix = "\n ";
53 
54 // get filename associated to this code
55 static std::string get_filename(const std::string& file_code)
56 {
57  if(!encode_filename) {
58  return file_code;
59  }
60 
61  std::stringstream s;
62  s << file_code;
63  int n = 0;
64  s >> std::hex >> n;
65 
66  for(const auto& p : file_number_map) {
67  if(p.second == n) {
68  return p.first;
69  }
70  }
71 
72  return "<unknown>";
73 }
74 
75 // Get code associated to this filename
76 static std::string get_file_code(const std::string& filename)
77 {
78  if(!encode_filename) {
79  return filename;
80  }
81 
82  // Current number of encountered filenames
83  static int current_file_number = 0;
84 
85  int& fnum = file_number_map[utils::escape(filename, " \\")];
86  if(fnum == 0) {
87  fnum = ++current_file_number;
88  }
89 
90  std::ostringstream shex;
91  shex << std::hex << fnum;
92 
93  return shex.str();
94 }
95 
96 // decode the filenames placed in a location
97 static std::string get_location(const std::string& loc)
98 {
99  std::string res;
100  std::vector<std::string> pos = utils::quoted_split(loc, ' ');
101 
102  if(pos.empty()) {
103  return res;
104  }
105 
106  std::vector<std::string>::const_iterator i = pos.begin(), end = pos.end();
107  while(true) {
108  res += get_filename(*(i++));
109 
110  if(i == end) {
111  break;
112  }
113 
114  res += ' ';
115  res += *(i++);
116 
117  if(i == end) {
118  break;
119  }
120 
121  res += ' ';
122  }
123 
124  return res;
125 }
126 
127 
128 // ==================================================================================
129 // PREPROC_DEFINE IMPLEMENTATION
130 // ==================================================================================
131 
133 {
134  return value == v.value && arguments == v.arguments;
135 }
136 
138 {
139  if(location < v.location) {
140  return true;
141  }
142 
143  if(v.location < location) {
144  return false;
145  }
146 
147  return linenum < v.linenum;
148 }
149 
150 void preproc_define::write_argument(config_writer& writer, const std::string& arg) const
151 {
152  const std::string key = "argument";
153 
154  writer.open_child(key);
155 
156  writer.write_key_val("name", arg);
157  writer.close_child(key);
158 }
159 
160 void preproc_define::write_argument(config_writer& writer, const std::string& arg, const std::string& default_value) const
161 {
162  const std::string key = "argument";
163 
164  writer.open_child(key);
165 
166  writer.write_key_val("name", arg);
167  writer.write_key_val("default", default_value);
168  writer.close_child(key);
169 }
170 
171 void preproc_define::write(config_writer& writer, const std::string& name) const
172 {
173  const std::string key = "preproc_define";
174  writer.open_child(key);
175 
176  writer.write_key_val("name", name);
177  writer.write_key_val("value", value);
178  writer.write_key_val("textdomain", textdomain);
179  writer.write_key_val("linenum", std::to_string(linenum));
180  writer.write_key_val("location", get_location(location));
181 
182  if(is_deprecated()) {
183  writer.open_child("deprecated");
184  writer.write_key_val("level", int(*deprecation_level));
185  writer.write_key_val("version", deprecation_version.str());
186  writer.write_key_val("message", deprecation_message);
187  writer.close_child("deprecated");
188  }
189 
190  for(const std::string& arg : arguments) {
191  write_argument(writer, arg);
192  }
193 
194  for(const auto& [key, default_value] : optional_arguments) {
195  write_argument(writer, key, default_value);
196  }
197 
198  writer.close_child(key);
199 }
200 
202 {
203  if(cfg.has_attribute("default")) {
204  optional_arguments.emplace(cfg["name"], cfg["default"]);
205  } else {
206  arguments.push_back(cfg["name"]);
207  }
208 }
209 
211 {
212  value = cfg["value"].str();
213  textdomain = cfg["textdomain"].str();
214  linenum = cfg["linenum"].to_int();
215  location = cfg["location"].str();
216 
217  if(auto deprecated = cfg.optional_child("deprecated")) {
218  deprecation_level = DEP_LEVEL(deprecated.value()["level"].to_int());
219  deprecation_version = deprecated.value()["version"].str();
220  deprecation_message = deprecated.value()["message"].str();
221  }
222 
223  for(const config& arg : cfg.child_range("argument")) {
224  read_argument(arg);
225  }
226 }
227 
229 {
230  map.try_emplace(cfg["name"], cfg);
231 }
232 
233 std::ostream& operator<<(std::ostream& stream, const preproc_define& def)
234 {
235  return stream << "value: " << def.value << " arguments: " << def.location;
236 }
237 
238 std::ostream& operator<<(std::ostream& stream, const preproc_map::value_type& def)
239 {
240  return stream << def.second;
241 }
242 
243 // ==================================================================================
244 // PREPROCESSOR BASE
245 // ==================================================================================
246 
248 
249 /**
250  * Base class for preprocessing an input.
251  */
253 {
255 
256 protected:
257  /**
258  * Sets up a new preprocessor for stream buffer \a t.
259  * Saves the current preprocessing context of #parent_. It will be automatically restored on destruction.
260  *
261  * It relies on preprocessor_streambuf so it's implemented after that class is declared.
262  */
264 
266 
267 public:
268  /**
269  * Restores the old preprocessing context.
270  * Appends location and domain directives to the buffer, so that the parser notices these changes.
271  */
272  virtual ~preprocessor();
273 
274  /** Allows specifying any actions that need to be called after the constructor completes. */
275  virtual void init()
276  {
277  }
278 
279  /**
280  * Preprocesses and sends some text to the #parent_ buffer.
281  * @return false when the input has no data left.
282  */
283  virtual bool get_chunk() = 0;
284 
286 
287  /** Returns the appropriate parsing mode for this preprocessor. */
288  virtual MODE parse_mode()
289  {
290  return NO_PARSING;
291  }
292 
293 private:
294  std::string old_textdomain_;
295  std::string old_location_;
296 
298 };
299 
300 
301 // ==================================================================================
302 // PREPROCESSOR BUFFER
303 // ==================================================================================
304 
305 /**
306  * Target for sending preprocessed output.
307  * Objects of this class can be plugged into an STL stream.
308  */
309 class preprocessor_streambuf : public std::streambuf
310 {
311 public:
313  : std::streambuf()
314  , defines_(def)
315  {
316  }
317 
318  /** Decodes the filenames placed in a location. */
319  std::string get_current_file();
320 
321  void error(const std::string&, int);
322  void warning(const std::string&, int);
323 
324  template<typename T, typename... A>
325  void add_preprocessor(A&&... args)
326  {
327  preprocessor_queue_.emplace_back(new T(*this, std::forward<A>(args)...));
328  preprocessor_queue_.back()->init();
329  }
330 
332  {
333  preprocessor_queue_.pop_back();
334  }
335 
336  int depth() const
337  {
338  return preprocessor_queue_.size();
339  }
340 
342  {
343  return preprocessor_queue_.empty() ? nullptr : preprocessor_queue_.back().get();
344  }
345 
346 private:
348  : std::streambuf()
349  , defines_(t.defines_)
350  , quoted_(t.quoted_)
351  {
352  }
353 
354  /** Inherited from basic_streambuf. */
355  virtual int underflow() override;
356 
357  /** Buffer read by the STL stream. */
358  std::string out_buffer_;
359 
360  /** Buffer filled by the _current_ preprocessor. */
361  std::stringstream buffer_;
362 
364 
365  std::string textdomain_{PACKAGE};
366  std::string location_;
367 
368  int linenum_{0};
369 
370  /**
371  * Input preprocessor queue.
372  * NOTE: This must be after textdomain_/location_/linenum_ because otherwise
373  * it causes a memory corruption error which causes Wesnoth to abort upon launch
374  * in some devices.
375  */
376  std::vector<std::unique_ptr<preprocessor>> preprocessor_queue_;
377 
378  /**
379  * Set to true if one preprocessor for this target started to read a string.
380  * Deeper-nested preprocessors are then forbidden to.
381  */
382  bool quoted_{false};
383 
384  friend class preprocessor;
385  friend class preprocessor_file;
386  friend class preprocessor_data;
387  friend class preprocessor_istream;
388 };
389 
390 /** Preprocessor constructor. */
392  : parent_(t)
393  , old_textdomain_(t.textdomain_)
394  , old_location_(t.location_)
395  , old_linenum_(t.linenum_)
396 {
397 }
398 
400 {
401  if(!old_location_.empty()) {
403  }
404 
406  parent_.buffer_ << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << old_textdomain_ << '\n';
407  }
408 
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 
474 {
475  unsigned nested_level = 0;
476 
477  preprocessor* pre = nullptr;
478 
479  // Iterate backwards over queue to get the last non-macro preprocessor.
480  for(auto p = preprocessor_queue_.rbegin(); p != preprocessor_queue_.rend(); ++p) {
481  pre = p->get();
482 
483  if(!pre || pre->parse_mode() == preprocessor::PARSES_FILE) {
484  break;
485  }
486 
487  if(pre->parse_mode() == preprocessor::PARSES_MACRO) {
488  ++nested_level;
489  }
490  }
491 
492  std::string res;
493  std::vector<std::string> pos = utils::quoted_split(location_, ' ');
494 
495  if(pos.size() <= 2 * nested_level) {
496  return res;
497  }
498 
499  return get_filename(pos[2 * nested_level]);
500 }
501 
502 std::string lineno_string(const std::string& lineno)
503 {
504  std::vector<std::string> pos = utils::quoted_split(lineno, ' ');
505  std::vector<std::string>::const_iterator i = pos.begin(), end = pos.end();
506  std::string included_from = preprocessor_error_detail_prefix + "included from ";
507  std::string res;
508 
509  while(i != end) {
510  const std::string& line = *(i++);
511 
512  if(!res.empty()) {
513  res += included_from;
514  }
515 
516  if(i != end) {
517  res += get_filename(*(i++));
518  } else {
519  res += "<unknown>";
520  }
521 
522  res += ':' + line;
523  }
524 
525  if(res.empty()) {
526  res = "???";
527  }
528 
529  return res;
530 }
531 
532 void preprocessor_streambuf::error(const std::string& error_type, int l)
533 {
534  std::string position, error;
535  std::ostringstream pos;
536 
537  pos << l << ' ' << location_;
538  position = lineno_string(pos.str());
539 
540  error = error_type + '\n';
541  error += "at " + position;
542 
543  ERR_PREPROC << error;
544 
546 }
547 
548 void preprocessor_streambuf::warning(const std::string& warning_type, int l)
549 {
550  std::string position, warning;
551  std::ostringstream pos;
552 
553  pos << l << ' ' << location_;
554  position = lineno_string(pos.str());
555 
556  warning = warning_type + '\n';
557  warning += "at " + position;
558 
559  WRN_PREPROC << warning;
560 }
561 
562 
563 // ==================================================================================
564 // PREPROCESSOR FILE
565 // ==================================================================================
566 
567 /**
568  * Specialized preprocessor for handling a file or a set of files.
569  * A preprocessor_file object is created when a preprocessor encounters an
570  * inclusion directive that resolves to a file or directory, e.g. '{themes/}'.
571  */
573 {
574 public:
575  /** Constructor. It relies on preprocessor_data so it's implemented after that class is declared. */
576  preprocessor_file(preprocessor_streambuf& t, const std::string& name, std::size_t symbol_index = -1);
577 
578  virtual void init() override;
579 
580  /**
581  * Inserts and processes the next file in the list of included files.
582  * @return false if there is no next file.
583  */
584  virtual bool get_chunk() override
585  {
586  while(pos_ != end_) {
587  const std::string& name = *(pos_++);
588 
589 #ifdef __cpp_lib_starts_ends_with
590  if(!name.ends_with(".cfg")) {
591 #else
592  // Use reverse iterator to optimize testing
593  if(name.size() < 5 || !std::equal(name.rbegin(), name.rbegin() + 4, "gfc.")) {
594 #endif
595  continue;
596  }
597 
599  return true;
600  }
601 
602  return false;
603  }
604 
605 private:
606  std::vector<std::string> files_;
607  std::vector<std::string>::const_iterator pos_, end_;
608 
609  const std::string& name_;
610 
612 };
613 
614 
615 // ==================================================================================
616 // PREPROCESSOR DATA
617 // ==================================================================================
618 
619 /**
620  * Specialized preprocessor for handling any kind of input stream.
621  * This is the core of the preprocessor.
622  */
624 {
625  /** Description of a preprocessing chunk. */
626  struct token_desc
627  {
628  enum class token_type {
629  start, // Toplevel
630  process_if, // Processing the "if" branch of a ifdef/ifndef (the "else" branch will be skipped)
631  process_else, // Processing the "else" branch of a ifdef/ifndef
632  skip_if, // Skipping the "if" branch of a ifdef/ifndef (the "else" branch, if any, will be processed)
633  skip_else, // Skipping the "else" branch of a ifdef/ifndef
634  string, // Processing a string
635  verbatim, // Processing a verbatim string
636  macro_space, // Processing between chunks of a macro call (skip spaces)
637  macro_chunk, // Processing inside a chunk of a macro call (stop on space or '(')
638  macro_parens // Processing a parenthesized macro argument
639  };
640 
641  token_desc(token_type type, const int stack_pos, const int linenum)
642  : type(type)
643  , stack_pos(stack_pos)
644  , linenum(linenum)
645  {
646  }
647 
649 
650  /** Starting position in #strings_ of the delayed text for this chunk. */
652  int linenum;
653  };
654 
655  /**
656  * Manages the lifetime of the @c std::istream pointer we own.
657  *
658  * Since @ref in_ uses the stream as well this object must be created
659  * before @ref in_ and destroyed after @ref in_ is destroyed.
660  */
662 
663  /** Input stream. */
665 
666  std::string directory_;
667 
668  /** Buffer for delayed input processing. */
669  std::vector<std::string> strings_;
670 
671  /** Mapping of macro arguments to their content. */
672  std::unique_ptr<std::map<std::string, std::string>> local_defines_;
673 
674  /** Stack of nested preprocessing chunks. */
675  std::vector<token_desc> tokens_;
676 
677  /**
678  * Set to true whenever input tokens cannot be directly sent to the target
679  * buffer. For instance, this happens with macro arguments. In that case,
680  * the output is redirected toward #strings_ until it can be processed.
681  */
683 
684  /**
685  * Non-zero when the preprocessor has to skip some input text.
686  * Increased whenever entering a conditional branch that is not useful,
687  * e.g. a ifdef that evaluates to false.
688  */
690  int linenum_;
691 
692  /** True iff we are currently parsing a macros content, otherwise false. */
694 
695  std::string read_word();
696  std::string read_line();
697  std::string read_rest_of_line();
698 
699  void skip_spaces();
700  void skip_eol();
701  void push_token(token_desc::token_type);
702  void pop_token();
703  void put(char);
704  void put(const std::string& /*, int change_line = 0 */);
705  void conditional_skip(bool skip);
706 
707 public:
710  const std::string& history,
711  const std::string& name,
712  int line,
713  const std::string& dir,
714  const std::string& domain,
715  std::unique_ptr<std::map<std::string, std::string>> defines,
716  bool is_define = false);
717 
718  virtual bool get_chunk() override;
719 
720  virtual preprocessor::MODE parse_mode() override
721  {
722  return is_define_ ? PARSES_MACRO : PARSES_FILE;
723  }
724 
729 };
730 
732 {
733  throw std::logic_error("don't compare tokens with characters");
734 }
735 
737 {
738  return rhs == lhs;
739 }
740 
742 {
743  return !(lhs == rhs);
744 }
745 
747 {
748  return rhs != lhs;
749 }
750 
751 /** preprocessor_file constructor. */
752 preprocessor_file::preprocessor_file(preprocessor_streambuf& t, const std::string& name, std::size_t symbol_index)
753  : preprocessor(t)
754  , files_()
755  , pos_()
756  , end_()
757  , name_(name)
759 {
760  if(is_directory_) {
761  filesystem::get_files_in_dir(name, &files_, nullptr,
765  );
766 
767  for(const std::string& fname : files_) {
768  std::size_t cpos = fname.rfind(" ");
769 
770  if(cpos != std::string::npos && cpos >= symbol_index) {
771  std::stringstream ss;
772  ss << "Found filename containing whitespace: '" << filesystem::base_name(fname)
773  << "' in included directory '" << name << "'.\nThe included symbol probably looks similar to '"
774  << filesystem::directory_name(fname.substr(symbol_index)) << "'";
775 
776  // TODO: find a real linenumber
777  parent_.error(ss.str(), -1);
778  }
779  }
780  } else {
781  // Handled in the init() function.
782  }
783 
784  pos_ = files_.begin();
785  end_ = files_.end();
786 }
787 
789 {
790  if(is_directory_) {
791  return;
792  }
793 
795 
796  if(!file_stream->good()) {
797  ERR_PREPROC << "Could not open file " << name_;
798  } else {
799  parent_.add_preprocessor<preprocessor_data>(std::move(file_stream), "",
802  );
803  }
804 }
805 
808  const std::string& history,
809  const std::string& name,
810  int linenum,
811  const std::string& directory,
812  const std::string& domain,
813  std::unique_ptr<std::map<std::string, std::string>> defines,
814  bool is_define)
815  : preprocessor(t)
816  , in_scope_(std::move(i))
817  , in_(*in_scope_)
818  , directory_(directory)
819  , strings_()
820  , local_defines_(std::move(defines))
821  , tokens_()
822  , slowpath_(0)
823  , skipping_(0)
824  , linenum_(linenum)
825  , is_define_(is_define)
826 {
827  std::ostringstream s;
828  s << history;
829 
830  if(!name.empty()) {
831  if(!history.empty()) {
832  s << ' ';
833  }
834 
835  s << get_file_code(name);
836  }
837 
838  if(!t.location_.empty()) {
839  s << ' ' << t.linenum_ << ' ' << t.location_;
840  }
841 
842  t.location_ = s.str();
843  t.linenum_ = linenum;
844 
845  t.buffer_ << INLINED_PREPROCESS_DIRECTIVE_CHAR << "line " << linenum << ' ' << t.location_ << '\n';
846 
847  if(t.textdomain_ != domain) {
848  t.buffer_ << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << domain << '\n';
849  t.textdomain_ = domain;
850  }
851 
853 }
854 
856 {
857  tokens_.emplace_back(t, strings_.size(), linenum_);
858 
860  // Macro expansions do not have any associated storage at start.
861  return;
863  /* Quoted strings are always inlined in the parent token. So
864  * they need neither storage nor metadata, unless the parent
865  * token is a macro expansion.
866  */
867  token_desc::token_type& outer_type = tokens_[tokens_.size() - 2].type;
868  if(outer_type != token_desc::token_type::macro_space) {
869  return;
870  }
871 
873  tokens_.back().stack_pos = strings_.size() + 1;
874  }
875 
876  std::ostringstream s;
877  if(!skipping_ && slowpath_) {
878  s << INLINED_PREPROCESS_DIRECTIVE_CHAR << "line " << linenum_ << ' ' << parent_.location_ << "\n"
879  << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << parent_.textdomain_ << '\n';
880  }
881 
882  strings_.push_back(s.str());
883 }
884 
886 {
887  token_desc::token_type inner_type = tokens_.back().type;
888  unsigned stack_pos = tokens_.back().stack_pos;
889 
890  tokens_.pop_back();
891 
892  token_desc::token_type& outer_type = tokens_.back().type;
893 
894  if(inner_type == token_desc::token_type::macro_parens) {
895  // Parenthesized macro arguments are left on the stack.
896  assert(outer_type == token_desc::token_type::macro_space);
897  return;
898  }
899 
900  if(inner_type == token_desc::token_type::string || inner_type == token_desc::token_type::verbatim) {
901  // Quoted strings are always inlined.
902  assert(stack_pos == strings_.size());
903  return;
904  }
905 
906  if(outer_type == token_desc::token_type::macro_space) {
907  /* A macro expansion does not have any associated storage.
908  * Instead, storage of the inner token is not discarded
909  * but kept as a new macro argument. But if the inner token
910  * was a macro expansion, it is about to be appended, so
911  * prepare for it.
912  */
914  strings_.erase(strings_.begin() + stack_pos, strings_.end());
915  strings_.emplace_back();
916  }
917 
918  assert(stack_pos + 1 == strings_.size());
920 
921  return;
922  }
923 
924  strings_.erase(strings_.begin() + stack_pos, strings_.end());
925 }
926 
928 {
929  while(true) {
930  int c = in_.peek();
931 
932  if(in_.eof() || (c != ' ' && c != '\t')) {
933  return;
934  }
935 
936  in_.get();
937  }
938 }
939 
941 {
942  while(true) {
943  int c = in_.get();
944 
945  if(c == '\n') {
946  ++linenum_;
947  return;
948  }
949 
950  if(in_.eof()) {
951  return;
952  }
953  }
954 }
955 
957 {
958  std::string res;
959 
960  while(true) {
961  int c = in_.peek();
962 
963  if(c == preprocessor_streambuf::traits_type::eof() || utils::portable_isspace(c)) {
964  // DBG_PREPROC << "(" << res << ")";
965  return res;
966  }
967 
968  in_.get();
969  res += static_cast<char>(c);
970  }
971 }
972 
974 {
975  std::string res;
976 
977  while(true) {
978  int c = in_.get();
979 
980  if(c == '\n') {
981  ++linenum_;
982  return res;
983  }
984 
985  if(in_.eof()) {
986  return res;
987  }
988 
989  if(c != '\r') {
990  res += static_cast<char>(c);
991  }
992  }
993 }
994 
996 {
997  std::string res;
998 
999  while(in_.peek() != '\n' && !in_.eof()) {
1000  int c = in_.get();
1001 
1002  if(c != '\r') {
1003  res += static_cast<char>(c);
1004  }
1005  }
1006 
1007  return res;
1008 }
1009 
1011 {
1012  if(skipping_) {
1013  return;
1014  }
1015 
1016  if(slowpath_) {
1017  strings_.back() += c;
1018  return;
1019  }
1020 
1021  int cond_linenum = c == '\n' ? linenum_ - 1 : linenum_;
1022 
1023  if(unsigned diff = cond_linenum - parent_.linenum_) {
1024  parent_.linenum_ = cond_linenum;
1025 
1026  if(diff <= parent_.location_.size() + 11) {
1027  parent_.buffer_ << std::string(diff, '\n');
1028  } else {
1030  }
1031  }
1032 
1033  if(c == '\n') {
1034  ++parent_.linenum_;
1035  }
1036 
1037  parent_.buffer_ << c;
1038 }
1039 
1040 void preprocessor_data::put(const std::string& s /*, int line_change*/)
1041 {
1042  if(skipping_) {
1043  return;
1044  }
1045 
1046  if(slowpath_) {
1047  strings_.back() += s;
1048  return;
1049  }
1050 
1051  parent_.buffer_ << s;
1052  // parent_.linenum_ += line_change;
1053 }
1054 
1056 {
1057  if(skip) {
1058  ++skipping_;
1059  }
1060 
1062 }
1063 
1065 {
1066  char c = static_cast<char>(in_.get());
1067  token_desc& token = tokens_.back();
1068 
1069  if(in_.eof()) {
1070  // The end of file was reached.
1071  // Make sure we don't have any incomplete tokens.
1072  char const* s;
1073 
1074  switch(token.type) {
1076  return false; // everything is fine
1081  s = "#ifdef or #ifndef";
1082  break;
1084  s = "Quoted string";
1085  break;
1087  s = "Verbatim string";
1088  break;
1091  s = "Macro substitution";
1092  break;
1094  s = "Macro argument";
1095  break;
1096  default:
1097  s = "???";
1098  }
1099 
1100  parent_.error(std::string(s) + " not terminated", token.linenum);
1101  }
1102 
1103  if(c == '\n') {
1104  ++linenum_;
1105  }
1106 
1107  if(c == static_cast<char>(INLINED_PREPROCESS_DIRECTIVE_CHAR)) {
1108  std::string buffer(1, c);
1109 
1110  while(true) {
1111  char d = static_cast<char>(in_.get());
1112 
1113  if(in_.eof() || d == '\n') {
1114  break;
1115  }
1116 
1117  buffer += d;
1118  }
1119 
1120  buffer += '\n';
1121  // line_change = 1-1 = 0
1122  put(buffer);
1124  put(c);
1125 
1126  if(c == '>' && in_.peek() == '>') {
1127  put(in_.get());
1128  pop_token();
1129  }
1130  } else if(c == '<' && in_.peek() == '<') {
1131  in_.get();
1133  put('<');
1134  put('<');
1135  } else if(c == '"') {
1137  parent_.quoted_ = false;
1138  put(c);
1139  pop_token();
1140  } else if(!parent_.quoted_) {
1141  parent_.quoted_ = true;
1143  put(c);
1144  } else {
1145  parent_.error("Nested quoted string", linenum_);
1146  }
1147  } else if(c == '{') {
1149  ++slowpath_;
1150  } else if(c == ')' && token.type == token_desc::token_type::macro_parens) {
1151  pop_token();
1152  } else if(c == '#' && !parent_.quoted_) {
1153  std::string command = read_word();
1154  bool comment = false;
1155 
1156  if(command == "define") {
1157  skip_spaces();
1158  int linenum = linenum_;
1159  std::vector<std::string> items = utils::split(read_line(), ' ');
1160  std::map<std::string, std::string> optargs;
1161 
1162  if(items.empty()) {
1163  parent_.error("No macro name found after #define directive", linenum);
1164  }
1165 
1166  std::string symbol = items.front();
1167  items.erase(items.begin());
1168  int found_arg = 0, found_enddef = 0, found_deprecate = 0;
1169  utils::optional<DEP_LEVEL> deprecation_level;
1170  std::string buffer, deprecation_detail;
1171  version_info deprecation_version = game_config::wesnoth_version;
1172  while(true) {
1173  if(in_.eof())
1174  break;
1175  char d = static_cast<char>(in_.get());
1176  if(d == '\n')
1177  ++linenum_;
1178  buffer += d;
1179  if(d == '#') {
1180  if(in_.peek() == 'a') {
1181  found_arg = 1;
1182  } else if(in_.peek() == 'd') {
1183  found_deprecate = 1;
1184  } else {
1185  found_enddef = 1;
1186  }
1187  } else {
1188  if(found_arg > 0 && ++found_arg == 4) {
1189 #ifdef __cpp_lib_starts_ends_with
1190  if(buffer.ends_with("arg")) {
1191 #else
1192  if(std::equal(buffer.end() - 3, buffer.end(), "arg")) {
1193 #endif
1194  buffer.erase(buffer.end() - 4, buffer.end());
1195 
1196  skip_spaces();
1197  std::string argname = read_word();
1198  skip_eol();
1199 
1200  std::string argbuffer;
1201 
1202  int found_endarg = 0;
1203  while(true) {
1204  if(in_.eof()) {
1205  break;
1206  }
1207 
1208  char e = static_cast<char>(in_.get());
1209  if(e == '\n') {
1210  ++linenum_;
1211  }
1212 
1213  argbuffer += e;
1214 
1215  if(e == '#') {
1216  found_endarg = 1;
1217  } else if(found_endarg > 0 && ++found_endarg == 7) {
1218 #ifdef __cpp_lib_starts_ends_with
1219  if(argbuffer.ends_with("endarg")) {
1220 #else
1221  if(std::equal(argbuffer.end() - 6, argbuffer.end(), "endarg")) {
1222 #endif
1223  argbuffer.erase(argbuffer.end() - 7, argbuffer.end());
1224  optargs[argname] = argbuffer;
1225  skip_eol();
1226  break;
1227  } else {
1228  parent_.error("Unterminated #arg definition", linenum_);
1229  }
1230  }
1231  }
1232  }
1233  }
1234 
1235  if(found_deprecate > 0 && ++found_deprecate == 11) {
1236 #ifdef __cpp_lib_starts_ends_with
1237  if(buffer.ends_with("deprecated")) {
1238 #else
1239  if(std::equal(buffer.end() - 10, buffer.end(), "deprecated")) {
1240 #endif
1241  buffer.erase(buffer.end() - 11, buffer.end());
1242  skip_spaces();
1243  try {
1245  if(deprecation_level) {
1246  deprecation_level = std::max(*deprecation_level, level);
1247  } else {
1248  deprecation_level = level;
1249  }
1250  } catch(const std::invalid_argument&) {
1251  // Meh, fall back to default of PREEMPTIVE...
1252  deprecation_level = DEP_LEVEL::PREEMPTIVE;
1253  }
1254  deprecation_version = game_config::wesnoth_version;
1255  if(deprecation_level == DEP_LEVEL::PREEMPTIVE || deprecation_level == DEP_LEVEL::FOR_REMOVAL) {
1256  skip_spaces();
1257  deprecation_version = std::max(deprecation_version, version_info(read_word()));
1258  }
1259  skip_spaces();
1260  if(!deprecation_detail.empty()){
1261  deprecation_detail += '\n';
1262  }
1263  deprecation_detail += read_rest_of_line();
1264  skip_eol();
1265  }
1266  }
1267 
1268  if(found_enddef > 0 && ++found_enddef == 7) {
1269 #ifdef __cpp_lib_starts_ends_with
1270  if(buffer.ends_with("enddef")) {
1271 #else
1272  if(std::equal(buffer.end() - 6, buffer.end(), "enddef")) {
1273 #endif
1274  break;
1275  } else {
1276  found_enddef = 0;
1277 #ifdef __cpp_lib_starts_ends_with
1278  if(buffer.ends_with("define")) {
1279 #else
1280  if(std::equal(buffer.end() - 6, buffer.end(), "define")) { // TODO: Maybe add support for
1281  // this? This would fill feature
1282  // request #21343
1283 #endif
1284  parent_.error(
1285  "Preprocessor error: #define is not allowed inside a #define/#enddef pair",
1286  linenum);
1287  }
1288  }
1289  }
1290  }
1291  }
1292 
1293  if(found_enddef != 7) {
1294  parent_.error("Unterminated preprocessor definition", linenum_);
1295  }
1296 
1297  if(!skipping_) {
1298  preproc_map::const_iterator old_i = parent_.defines_.find(symbol);
1299  if(old_i != parent_.defines_.end()) {
1300  std::ostringstream new_pos, old_pos;
1301  const preproc_define& old_d = old_i->second;
1302 
1303  new_pos << linenum << ' ' << parent_.location_;
1304  old_pos << old_d.linenum << ' ' << old_d.location;
1305 
1306  WRN_PREPROC << "Redefining macro " << symbol << " without explicit #undef at "
1307  << lineno_string(new_pos.str()) << '\n'
1308  << "previously defined at " << lineno_string(old_pos.str());
1309  }
1310 
1311  buffer.erase(buffer.end() - 7, buffer.end());
1312  parent_.defines_.insert_or_assign(symbol,
1313  preproc_define(buffer, items, optargs, parent_.textdomain_, linenum, parent_.location_,
1314  deprecation_detail, deprecation_level, deprecation_version));
1315 
1316  LOG_PREPROC << "defining macro " << symbol << " (location " << get_location(parent_.location_) << ")";
1317  }
1318  } else if(command == "ifdef" || command == "ifndef") {
1319  const bool negate = command[2] == 'n';
1320  skip_spaces();
1321  const std::string& symbol = read_word();
1322  if(symbol.empty()) {
1323  parent_.error("No macro argument found after #ifdef/#ifndef directive", linenum_);
1324  }
1325  bool found = parent_.defines_.count(symbol) != 0;
1326  DBG_PREPROC << "testing for macro " << symbol << ": " << (found ? "defined" : "not defined");
1327  conditional_skip(negate ? found : !found);
1328  } else if(command == "ifhave" || command == "ifnhave") {
1329  const bool negate = command[2] == 'n';
1330  skip_spaces();
1331  const std::string& symbol = read_word();
1332  if(symbol.empty()) {
1333  parent_.error("No path argument found after #ifhave/#ifnhave directive", linenum_);
1334  }
1335  bool found = filesystem::get_wml_location(symbol, directory_).has_value();
1336  DBG_PREPROC << "testing for file or directory " << symbol << ": " << (found ? "found" : "not found");
1337  conditional_skip(negate ? found : !found);
1338  } else if(command == "ifver" || command == "ifnver") {
1339  const bool negate = command[2] == 'n';
1340 
1341  skip_spaces();
1342  const std::string& vsymstr = read_word();
1343  skip_spaces();
1344  const std::string& vopstr = read_word();
1345  skip_spaces();
1346  const std::string& vverstr = read_word();
1347 
1348  const VERSION_COMP_OP vop = parse_version_op(vopstr);
1349 
1350  if(vop == OP_INVALID) {
1351  parent_.error("Invalid #ifver/#ifnver operator", linenum_);
1352  } else if(parent_.defines_.count(vsymstr) != 0) {
1353  const preproc_define& sym = parent_.defines_[vsymstr];
1354 
1355  if(!sym.arguments.empty()) {
1356  parent_.error("First argument macro in #ifver/#ifnver should not require arguments", linenum_);
1357  }
1358 
1359  version_info const version1(sym.value);
1360  version_info const version2(vverstr);
1361 
1362  const bool found = do_version_check(version1, vop, version2);
1363  DBG_PREPROC << "testing version '" << version1.str() << "' against '" << version2.str() << "' ("
1364  << vopstr << "): " << (found ? "match" : "no match");
1365 
1366  conditional_skip(negate ? found : !found);
1367  } else {
1368  std::string err = "Undefined macro in #ifver/#ifnver first argument: '";
1369  err += vsymstr;
1370  err += "'";
1372  }
1373  } else if(command == "else") {
1375  pop_token();
1376  --skipping_;
1379  pop_token();
1380  ++skipping_;
1382  } else {
1383  parent_.error("Unexpected #else", linenum_);
1384  }
1385  } else if(command == "endif") {
1386  switch(token.type) {
1389  --skipping_;
1392  break;
1393  default:
1394  parent_.error("Unexpected #endif", linenum_);
1395  }
1396  pop_token();
1397  } else if(command == "textdomain") {
1398  skip_spaces();
1399  const std::string& s = read_word();
1400  if(s != parent_.textdomain_) {
1401  put("#textdomain ");
1402  put(s);
1403  parent_.textdomain_ = s;
1404  }
1405  comment = true;
1406  } else if(command == "enddef") {
1407  parent_.error("Unexpected #enddef", linenum_);
1408  } else if(command == "undef") {
1409  skip_spaces();
1410  const std::string& symbol = read_word();
1411  if(!skipping_) {
1412  parent_.defines_.erase(symbol);
1413  LOG_PREPROC << "undefine macro " << symbol << " (location " << get_location(parent_.location_) << ")";
1414  }
1415  } else if(command == "error") {
1416  if(!skipping_) {
1417  skip_spaces();
1418  std::ostringstream error;
1419  error << "#error: \"" << read_rest_of_line() << '"';
1420  parent_.error(error.str(), linenum_);
1421  } else
1422  DBG_PREPROC << "Skipped an error";
1423  } else if(command == "warning") {
1424  if(!skipping_) {
1425  skip_spaces();
1426  std::ostringstream warning;
1427  warning << "#warning: \"" << read_rest_of_line() << '"';
1428  parent_.warning(warning.str(), linenum_);
1429  } else {
1430  DBG_PREPROC << "Skipped a warning";
1431  }
1432  } else if(command == "deprecated") {
1433  // The current file is deprecated, so print a message
1434  skip_spaces();
1436  try {
1438  } catch(const std::invalid_argument&) {
1439  // Meh, just fall back to the default of PREEMPTIVE...
1440  }
1443  skip_spaces();
1444  version = version_info(read_word());
1445  }
1446  skip_spaces();
1447  std::string detail = read_rest_of_line();
1449  } else {
1451  }
1452 
1453  skip_eol();
1454  if(comment) {
1455  put('\n');
1456  }
1458  if(c == '(') {
1459  // If a macro argument was started, it is implicitly ended.
1462  } else if(utils::portable_isspace(c)) {
1463  // If a macro argument was started, it is implicitly ended.
1465  } else if(c == '}') {
1466  --slowpath_;
1467  if(skipping_) {
1468  pop_token();
1469  return true;
1470  }
1471 
1472  // FIXME: is this obsolete?
1473  // if (token.type == token_desc::MACRO_SPACE) {
1474  // if (!strings_.back().empty()) {
1475  // std::ostringstream error;
1476  // std::ostringstream location;
1477  // error << "Can't parse new macro parameter with a macro call scope open";
1478  // location<<linenum_<<' '<<parent_.location_;
1479  // parent_.error(error.str(), location.str());
1480  // }
1481  // strings_.pop_back();
1482  //}
1483 
1484  if(strings_.size() <= static_cast<std::size_t>(token.stack_pos)) {
1485  parent_.error("No macro or file substitution target specified", linenum_);
1486  }
1487 
1488  std::string symbol = strings_[token.stack_pos];
1489  std::string::size_type pos;
1490  while((pos = symbol.find(INLINED_PREPROCESS_DIRECTIVE_CHAR)) != std::string::npos) {
1491  std::string::iterator b = symbol.begin(); // invalidated at each iteration
1492  symbol.erase(b + pos, b + symbol.find('\n', pos + 1) + 1);
1493  }
1494 
1495  std::map<std::string, std::string>::const_iterator arg;
1496  preproc_map::const_iterator macro;
1497 
1498  // If this is a known pre-processing symbol, then we insert it,
1499  // otherwise we assume it's a file name to load.
1500  if(symbol == current_file_str && strings_.size() - token.stack_pos == 1) {
1501  pop_token();
1503  } else if(symbol == current_dir_str && strings_.size() - token.stack_pos == 1) {
1504  pop_token();
1506  } else if(symbol == left_curly_str && strings_.size() - token.stack_pos == 1) {
1507  pop_token();
1508  put("{");
1509  } else if(symbol == right_curly_str && strings_.size() - token.stack_pos == 1) {
1510  pop_token();
1511  put("}");
1512  } else if(local_defines_ && (arg = local_defines_->find(symbol)) != local_defines_->end()) {
1513  if(strings_.size() - token.stack_pos != 1) {
1514  std::ostringstream error;
1515  error << "Macro argument '" << symbol << "' does not expect any arguments";
1516  parent_.error(error.str(), linenum_);
1517  }
1518 
1519  std::ostringstream v;
1520  v << arg->second << INLINED_PREPROCESS_DIRECTIVE_CHAR << "line " << linenum_ << ' ' << parent_.location_ << "\n"
1521  << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << parent_.textdomain_ << '\n';
1522 
1523  pop_token();
1524  put(v.str());
1525  } else if(parent_.depth() < 100 && (macro = parent_.defines_.find(symbol)) != parent_.defines_.end()) {
1526  const preproc_define& val = macro->second;
1527  std::size_t nb_arg = strings_.size() - token.stack_pos - 1;
1528  std::size_t optional_arg_num = 0;
1529 
1530  auto defines = std::make_unique<std::map<std::string, std::string>>();
1531  const std::string& dir = filesystem::directory_name(val.location.substr(0, val.location.find(' ')));
1532 
1533  if(val.is_deprecated()) {
1535  }
1536 
1537  for(std::size_t i = 0; i < nb_arg; ++i) {
1538  if(i < val.arguments.size()) {
1539  // Normal mandatory arguments
1540 
1541  (*defines)[val.arguments[i]] = strings_[token.stack_pos + i + 1];
1542  } else {
1543  // These should be optional argument overrides
1544 
1545  std::string str = strings_[token.stack_pos + i + 1];
1546  std::size_t equals_pos = str.find_first_of("=");
1547 
1548  if(equals_pos != std::string::npos) {
1549  std::size_t argname_pos = str.substr(0, equals_pos).find_last_of(" \n") + 1;
1550 
1551  std::string argname = str.substr(argname_pos, equals_pos - argname_pos);
1552 
1553  if(val.optional_arguments.find(argname) != val.optional_arguments.end()) {
1554  (*defines)[argname] = str.substr(equals_pos + 1);
1555 
1556  optional_arg_num++;
1557 
1558  DBG_PREPROC << "Found override for " << argname << " in call to macro " << symbol;
1559  } else {
1560  std::ostringstream warning;
1561  warning << "Unrecognized optional argument passed to macro '" << symbol << "': '"
1562  << argname << "'";
1563  parent_.warning(warning.str(), linenum_);
1564 
1565  optional_arg_num++; // To prevent the argument number check from blowing up
1566  }
1567  }
1568  }
1569  }
1570 
1571  // If the macro definition has any optional arguments, insert their defaults
1572  if(val.optional_arguments.size() > 0) {
1573  for(const auto& [argument, argument_value] : val.optional_arguments) {
1574  if(defines->find(argument) == defines->end()) {
1575  std::unique_ptr<preprocessor_streambuf> buf(new preprocessor_streambuf(parent_));
1576 
1577  buf->textdomain_ = parent_.textdomain_;
1578  std::istream in(buf.get());
1579 
1580  filesystem::scoped_istream buffer{new std::istringstream(argument_value)};
1581 
1582  auto temp_defines = std::make_unique<std::map<std::string, std::string>>();
1583  temp_defines->insert(defines->begin(), defines->end());
1584 
1585  buf->add_preprocessor<preprocessor_data>(
1586  std::move(buffer), val.location, "", val.linenum, dir, val.textdomain, std::move(temp_defines), false);
1587 
1588  std::ostringstream res;
1589  res << in.rdbuf();
1590 
1591  DBG_PREPROC << "Setting default for optional argument " << argument << " in macro "
1592  << symbol;
1593 
1594  (*defines)[argument] = res.str();
1595  }
1596  }
1597  }
1598 
1599  if(nb_arg - optional_arg_num != val.arguments.size()) {
1600  const std::vector<std::string>& locations = utils::quoted_split(val.location, ' ');
1601  const std::string filename = locations.empty() ? "<command-line>" : get_filename(locations[0]);
1602  std::ostringstream error;
1603  error << "Preprocessor symbol '" << symbol << "' defined at " << filename << ":"
1604  << val.linenum << " expects " << val.arguments.size() << " arguments, but has "
1605  << nb_arg - optional_arg_num << " arguments";
1606  parent_.error(error.str(), linenum_);
1607  }
1608 
1609  filesystem::scoped_istream buffer{new std::istringstream(val.value)};
1610 
1611  pop_token();
1612 
1613  if(!slowpath_) {
1614  DBG_PREPROC << "substituting macro " << symbol;
1615 
1617  std::move(buffer), val.location, "", val.linenum, dir, val.textdomain, std::move(defines), true);
1618  } else {
1619  DBG_PREPROC << "substituting (slow) macro " << symbol;
1620 
1621  std::unique_ptr<preprocessor_streambuf> buf(new preprocessor_streambuf(parent_));
1622 
1623  // Make the nested preprocessor_data responsible for
1624  // restoring our current textdomain if needed.
1625  buf->textdomain_ = parent_.textdomain_;
1626 
1627  std::ostringstream res;
1628  {
1629  std::istream in(buf.get());
1630  buf->add_preprocessor<preprocessor_data>(
1631  std::move(buffer), val.location, "", val.linenum, dir, val.textdomain, std::move(defines), true);
1632 
1633  res << in.rdbuf();
1634  }
1635 
1636  put(res.str());
1637  }
1638  } else if(parent_.depth() < 40) {
1639  LOG_PREPROC << "Macro definition not found for " << symbol << ", attempting to open as file.";
1640  pop_token();
1641 
1642  if(auto nfname = filesystem::get_wml_location(symbol, directory_)) {
1643  if(!slowpath_)
1644  // nfname.size() - symbol.size() gives you an index into nfname
1645  // This does not necessarily match the symbol though, as it can start with ~ or ./
1646  parent_.add_preprocessor<preprocessor_file>(nfname.value(), nfname->size() - symbol.size());
1647  else {
1648  std::unique_ptr<preprocessor_streambuf> buf(new preprocessor_streambuf(parent_));
1649 
1650  std::ostringstream res;
1651  {
1652  std::istream in(buf.get());
1653  buf->add_preprocessor<preprocessor_file>(nfname.value(), nfname->size() - symbol.size());
1654 
1655  res << in.rdbuf();
1656  }
1657 
1658  put(res.str());
1659  }
1660  } else {
1661  std::ostringstream error;
1662  error << "Macro/file '" << symbol << "' is missing";
1663  parent_.error(error.str(), linenum_);
1664  }
1665  } else {
1666  parent_.error("Too many nested preprocessing inclusions", linenum_);
1667  }
1668  } else if(!skipping_) {
1670  std::ostringstream s;
1671  s << INLINED_PREPROCESS_DIRECTIVE_CHAR << "line " << linenum_ << ' ' << parent_.location_ << "\n"
1672  << INLINED_PREPROCESS_DIRECTIVE_CHAR << "textdomain " << parent_.textdomain_ << '\n';
1673 
1674  strings_.push_back(s.str());
1676  }
1677  put(c);
1678  }
1679  } else {
1680  put(c);
1681  }
1682 
1683  return true;
1684 }
1685 
1686 
1687 // ==================================================================================
1688 // PREPROCESSOR ISTREAM
1689 // ==================================================================================
1690 
1691 class preprocessor_istream : public std::istream
1692 {
1693 public:
1695  : std::istream(nullptr)
1696  , owned_defines_()
1697  , buffer_(owned_defines_)
1698  {
1699  }
1700 
1702  : std::istream(nullptr)
1703  , owned_defines_()
1704  , buffer_(defines)
1705  {
1706  }
1707 
1709  {
1710  clear(std::ios_base::goodbit);
1711  exceptions(std::ios_base::goodbit);
1712  rdbuf(nullptr);
1713  }
1714 
1715  template<typename Preprocessor, typename... Args>
1716  void process(Args&&... args)
1717  {
1718  // Take ownership of the processing buffer.
1719  rdbuf(&buffer_);
1720 
1721  // Begin processing.
1722  buffer_.add_preprocessor<Preprocessor>(std::forward<Args>(args)...);
1723  }
1724 
1725 private:
1726  /** Defines map to be used if no external map is given. */
1728 
1729  /** The underlying processing buffer. */
1731 };
1732 
1733 // ==================================================================================
1734 // FREE-STANDING FUNCTIONS
1735 // ==================================================================================
1736 
1737 filesystem::scoped_istream preprocess_file(const std::string& fname, preproc_map& defines)
1738 {
1739  log_scope("preprocessing file " + fname + " ...");
1740 
1741  auto stream = std::make_unique<preprocessor_istream>(defines);
1742  stream->process<preprocessor_file>(fname);
1743  return stream;
1744 }
1745 
1747 {
1748  log_scope("preprocessing file " + fname + " ...");
1749 
1750  auto stream = std::make_unique<preprocessor_istream>();
1751  stream->process<preprocessor_file>(fname);
1752  return stream;
1753 }
1754 
1756  const std::string& contents, const std::string& textdomain)
1757 {
1758  log_scope("preprocessing string " + contents.substr(0, 10) + " ...");
1759 
1760  auto stream = std::make_unique<preprocessor_istream>();
1761  stream->process<preprocessor_data>(
1762  std::make_unique<std::istringstream>(contents), "<string>", "", 1, game_config::path, textdomain, nullptr);
1763 
1764  return stream;
1765 }
1766 
1768  const std::string& contents, const std::string& textdomain, preproc_map& defines)
1769 {
1770  log_scope("preprocessing string " + contents.substr(0, 10) + " ...");
1771 
1772  auto stream = std::make_unique<preprocessor_istream>(defines);
1773  stream->process<preprocessor_data>(
1774  std::make_unique<std::istringstream>(contents), "<string>", "", 1, game_config::path, textdomain, nullptr);
1775 
1776  return stream;
1777 }
1778 
1779 void preprocess_resource(const std::string& res_name,
1780  preproc_map* defines_map,
1781  bool write_cfg,
1782  bool write_plain_cfg,
1783  const std::string& parent_directory)
1784 {
1785  if(filesystem::is_directory(res_name)) {
1786  std::vector<std::string> dirs, files;
1787 
1790 
1791  // Subdirectories
1792  for(const std::string& dir : dirs) {
1793  LOG_PREPROC << "processing sub-dir: " << dir;
1794  preprocess_resource(dir, defines_map, write_cfg, write_plain_cfg, parent_directory);
1795  }
1796 
1797  // Files in current directory
1798  for(const std::string& file : files) {
1799  if(filesystem::is_cfg(file)) {
1800  preprocess_resource(file, defines_map, write_cfg, write_plain_cfg, parent_directory);
1801  }
1802  }
1803 
1804  return;
1805  }
1806 
1807  LOG_PREPROC << "processing resource: " << res_name;
1808 
1809  // disable filename encoding to get clear #line in cfg.plain
1810  encode_filename = false;
1811 
1812  filesystem::scoped_istream stream = defines_map
1813  ? preprocess_file(res_name, *defines_map)
1814  : preprocess_file(res_name);
1815 
1816  std::stringstream ss;
1817 
1818  // Set the failbit so if we get any preprocessor exceptions (e.g.:preproc_config::error)
1819  // they will be propagated in the main program, instead of just setting the
1820  // failbit on the stream. This was necessary in order for the MSVC and GCC
1821  // binaries to behave the same way.
1822  ss.exceptions(std::ios_base::failbit);
1823 
1824  ss << (*stream).rdbuf();
1825 
1826  LOG_PREPROC << "processing finished";
1827 
1828  if(write_cfg || write_plain_cfg) {
1829  std::string streamContent = ss.str();
1830  config cfg = io::read(streamContent);
1831 
1832  const std::string preproc_res_name = parent_directory + "/" + filesystem::base_name(res_name);
1833 
1834  // Write the processed cfg file
1835  if(write_cfg) {
1836  LOG_PREPROC << "writing cfg file: " << preproc_res_name;
1837 
1839  io::write(*filesystem::ostream_file(preproc_res_name), cfg, 0, true);
1840  }
1841 
1842  // Write the plain cfg file
1843  if(write_plain_cfg) {
1844  LOG_PREPROC << "writing plain cfg file: " << (preproc_res_name + ".plain");
1845 
1847  filesystem::write_file(preproc_res_name + ".plain", streamContent);
1848  }
1849  }
1850 }
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
unsigned in
If equal to search_counter, the node is off the list.
Definition: astarsearch.cpp:70
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:157
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
child_itors child_range(std::string_view key)
Definition: config.cpp:268
bool has_attribute(std::string_view key) const
Definition: config.cpp:157
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()
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.
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.
preprocessor_istream(preproc_map &defines)
void process(Args &&... args)
preproc_map owned_defines_
Defines map to be used if no external map is given.
preprocessor_streambuf buffer_
The underlying processing buffer.
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.
std::vector< std::unique_ptr< preprocessor > > preprocessor_queue_
Input preprocessor queue.
virtual int underflow() override
Inherited from basic_streambuf.
std::string out_buffer_
Buffer read by the STL stream.
std::string get_current_file()
Decodes the filenames placed in a location.
preprocessor_streambuf(preproc_map &def)
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.
std::string old_location_
virtual ~preprocessor()
Restores the old preprocessing context.
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
const config * cfg
std::size_t i
Definition: function.cpp:1031
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:275
void clear()
Clear the current render target.
Definition: draw.cpp:42
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:189
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
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:466
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:402
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::string path
Definition: filesystem.cpp:106
const version_info wesnoth_version(VERSION)
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:610
void write(std::ostream &out, const configr_of &cfg, unsigned int level, bool strong_quotes)
Definition: parser.cpp:748
logger & err()
Definition: log.cpp:339
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...
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:156
bool portable_isspace(const char c)
std::string escape(std::string_view 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 to_string(const Range &range, const Func &op)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
#define DBG_PREPROC
static lg::log_domain log_preprocessor("preprocessor")
filesystem::scoped_istream preprocess_string(const std::string &contents, const std::string &textdomain)
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
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
static void insert(preproc_map &, const config &)
std::map< std::string, std::string > optional_arguments
bool operator==(const preproc_define &) const
utils::optional< DEP_LEVEL > deprecation_level
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.
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:83
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