The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
30#include "game_version.hpp"
31#include "wesconfig.h"
32#include "deprecation.hpp"
33
34#include <stdexcept>
35
36static 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
42static const std::string current_file_str = "CURRENT_FILE";
43static const std::string current_dir_str = "CURRENT_DIRECTORY";
44static const std::string left_curly_str = "LEFT_BRACE";
45static const std::string right_curly_str = "RIGHT_BRACE";
46
47// map associating each filename encountered to a number
48static std::map<std::string, int> file_number_map;
49
50static bool encode_filename = true;
51
52static std::string preprocessor_error_detail_prefix = "\n ";
53
54// get filename associated to this code
55static 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
76static 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
97static 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
150void 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
160void 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
171void 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
233std::ostream& operator<<(std::ostream& stream, const preproc_define& def)
234{
235 return stream << "value: " << def.value << " arguments: " << def.location;
236}
237
238std::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
256protected:
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
267public:
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
293private:
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 */
309class preprocessor_streambuf : public std::streambuf
310{
311public:
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
346private:
348 : std::streambuf()
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;
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
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 {
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
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
502std::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
532void 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
544
546}
547
548void 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
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{
574public:
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
605private:
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. */
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. */
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 */
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
707public:
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
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. */
752preprocessor_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)
758 , is_directory_(filesystem::is_directory(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
1040void 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(token.type != token_desc::token_type::string && 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 {
1244 DEP_LEVEL level = DEP_LEVEL(std::stoi(read_word()));
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 += "'";
1371 parent_.error(err, linenum_);
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);
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 {
1437 level = DEP_LEVEL(std::stoi(read_word()));
1438 } catch(const std::invalid_argument&) {
1439 // Meh, just fall back to the default of PREEMPTIVE...
1440 }
1442 if(level == DEP_LEVEL::PREEMPTIVE || level == DEP_LEVEL::FOR_REMOVAL) {
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
1691class preprocessor_istream : public std::istream
1692{
1693public:
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
1725private:
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
1737filesystem::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
1779void 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
unsigned in
If equal to search_counter, the node is off the list.
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.
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_
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)
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.
bool quoted_
Set to true if one preprocessor for this target started to read a string.
preprocessor * current() const
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.
bool operator==(const config &a, const config &b)
Definition config.cpp:1306
Definitions for the interface to Wesnoth Markup Language (WML).
bool operator!=(const config &a, const config &b)
Definition config.hpp:152
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
DEP_LEVEL
See https://wiki.wesnoth.org/CompatibilityStandards for more info.
const config * cfg
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 get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, name_mode mode=name_mode::FILE_NAME_ONLY, filter_mode filter=filter_mode::NO_FILTER, reorder_mode reorder=reorder_mode::DONT_REORDER, file_tree_checksum *checksum=nullptr)
Get a list of all files and/or directories in a given directory.
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=false)
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=utils::nullopt)
Returns a translated path to the actual file or directory, if it exists.
bool create_directory_if_missing_recursive(const std::string &dirname)
Creates a recursive directory tree if it does not exist already.
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode=std::ios_base::binary, bool create_directory=true)
std::unique_ptr< std::istream > scoped_istream
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode=std::ios_base::binary)
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.
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error=true)
std::string path
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
STL namespace.
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(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)
#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_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 i
#define d
#define e
#define c
#define b