The Battle for Wesnoth  1.19.20+dev
parser.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2025
3  by Philippe Plantier <ayin@anathas.org>
4  Copyright (C) 2005 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
5  Copyright (C) 2003 by David White <dave@whitevine.net>
6  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
7 
8  This program is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY.
14 
15  See the COPYING file for more details.
16 */
17 
18 /**
19  * @file
20  * Read/Write & analyze WML- and config-files.
21  */
22 
23 #include "serialization/parser.hpp"
24 
25 #include "config.hpp"
26 #include "gettext.hpp"
27 #include "log.hpp"
31 #include "utils/charconv.hpp"
32 #include "wesconfig.h"
33 
34 #include <boost/algorithm/string/replace.hpp>
35 #include <boost/iostreams/filter/bzip2.hpp>
36 #include <boost/iostreams/filtering_stream.hpp>
37 
38 #if defined(_MSC_VER)
39 #pragma warning(push)
40 #pragma warning(disable : 4456)
41 #pragma warning(disable : 4458)
42 #endif
43 
44 #include <boost/iostreams/filter/gzip.hpp>
45 
46 #if defined(_MSC_VER)
47 #pragma warning(pop)
48 #endif
49 
50 #include <stack>
51 
52 static lg::log_domain log_config("config");
53 #define ERR_CF LOG_STREAM(err, log_config)
54 #define WRN_CF LOG_STREAM(warn, log_config)
55 #define LOG_CF LOG_STREAM(info, log_config)
56 
57 constexpr std::size_t max_recursion_levels = 1000;
58 
59 namespace io
60 {
61 namespace
62 {
63 // ==================================================================================
64 // PARSER
65 // ==================================================================================
66 
67 class parser
68 {
69 public:
70  parser(std::istream& in, abstract_validator* validator = nullptr)
71  : tok_(in)
72  , validator_(validator)
73  , elements()
74  {
75  }
76 
77  config operator()();
78 
79 private:
80  void parse_element();
81  void parse_variable();
82 
83  std::string lineno_string(utils::string_map& map,
84  const std::string& lineno,
85  const std::string& error_string,
86  const std::string& hint_string = "",
87  const std::string& debug_string = "");
88 
89  /** @throws config::error */
90  [[noreturn]] void error(const std::string& message, const std::string& pos_format = "");
91 
92  struct element
93  {
94 #ifndef __cpp_aggregate_paren_init
95  element(config* cfg, const std::string& name, int start_line = 0, const std::string& file = "")
96  : cfg(cfg)
97  , name(name)
98  , start_line(start_line)
99  , file(file)
100  {
101  }
102 #endif
103 
104  config* cfg{};
105  std::string name{};
106  int start_line{};
107  std::string file{};
108  };
109 
110  tokenizer tok_;
111  abstract_validator* validator_;
112 
113  std::stack<element> elements;
114 };
115 
116 config parser::operator()()
117 {
118  config res;
119  elements.emplace(&res, "");
120 
121  if(validator_) {
122  validator_->open_tag("", res, tok_.get_start_line(), tok_.get_file());
123  }
124 
125  do {
126  tok_.next_token();
127 
128  switch(tok_.current_token().type) {
129  case token::NEWLINE:
130  continue;
131 
132  case token::OPEN_BRACKET:
133  parse_element();
134  break;
135 
136  case token::STRING:
137  parse_variable();
138  break;
139 
140  default:
141  if(static_cast<unsigned char>(tok_.current_token().value[0]) == 0xEF &&
142  static_cast<unsigned char>(tok_.next_token().value[0]) == 0xBB &&
143  static_cast<unsigned char>(tok_.next_token().value[0]) == 0xBF
144  ) {
145  utils::string_map i18n_symbols;
146  std::stringstream ss;
147  ss << tok_.get_start_line() << " " << tok_.get_file();
148  ERR_CF << lineno_string(i18n_symbols, ss.str(), "Skipping over a utf8 BOM at $pos");
149  } else {
150  error(_("Unexpected characters at line start"));
151  }
152 
153  break;
154 
155  case token::END:
156  break;
157  }
158  } while(tok_.current_token().type != token::END);
159 
160  // The main element should be there. If it is not, this is a parser error.
161  assert(!elements.empty());
162 
163  if(validator_) {
164  element& el = elements.top();
165  validator_->validate(*el.cfg, el.name, el.start_line, el.file);
166  validator_->close_tag();
167  }
168 
169  if(elements.size() != 1) {
170  utils::string_map i18n_symbols;
171  i18n_symbols["tag"] = elements.top().name;
172 
173  std::stringstream ss;
174  ss << elements.top().start_line << " " << elements.top().file;
175 
176  error(lineno_string(i18n_symbols, ss.str(),
177  _("Missing closing tag for tag [$tag]"),
178  _("expected at $pos")),
179  _("opened at $pos")
180  );
181  }
182 
183  return res;
184 }
185 
186 void parser::parse_element()
187 {
188  tok_.next_token();
189 
190  std::string elname;
191  config* current_element = nullptr;
192  config* parent = nullptr;
193 
194  switch(tok_.current_token().type) {
195  case token::STRING: // [element]
196  elname = tok_.current_token().value;
197 
198  if(tok_.next_token().type != token::CLOSE_BRACKET) {
199  error(_("Unterminated [element] tag"));
200  }
201 
202  // Add the element
203  parent = elements.top().cfg;
204  current_element = &(parent->add_child(elname));
205  elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
206 
207  if(validator_) {
208  validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
209  }
210 
211  break;
212 
213  case token::PLUS: // [+element]
214  if(tok_.next_token().type != token::STRING) {
215  error(_("Invalid tag name"));
216  }
217 
218  elname = tok_.current_token().value;
219 
220  if(tok_.next_token().type != token::CLOSE_BRACKET) {
221  error(_("Unterminated [+element] tag"));
222  }
223 
224  // Find the last child of the current element whose name is element
225  parent = elements.top().cfg;
226  if(auto c = parent->optional_child(elname, -1)) {
227  current_element = c.ptr();
228 
229  if(validator_) {
230  validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file(), true);
231  }
232  } else {
233  current_element = &parent->add_child(elname);
234 
235  if(validator_) {
236  validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
237  }
238  }
239 
240  elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
241  break;
242 
243  case token::SLASH: // [/element]
244  if(tok_.next_token().type != token::STRING) {
245  error(_("Invalid closing tag name"));
246  }
247 
248  elname = tok_.current_token().value;
249 
250  if(tok_.next_token().type != token::CLOSE_BRACKET) {
251  error(_("Unterminated closing tag"));
252  }
253 
254  if(elements.size() <= 1) {
255  error(_("Unexpected closing tag"));
256  }
257 
258  if(elname != elements.top().name) {
259  utils::string_map i18n_symbols;
260  i18n_symbols["tag1"] = elements.top().name;
261  i18n_symbols["tag2"] = elname;
262 
263  std::stringstream ss;
264  ss << elements.top().start_line << " " << elements.top().file;
265 
266  error(lineno_string(i18n_symbols, ss.str(),
267  _("Found invalid closing tag [/$tag2] for tag [$tag1]"),
268  _("opened at $pos")),
269  _("closed at $pos")
270  );
271  }
272 
273  if(validator_) {
274  element& el = elements.top();
275  validator_->validate(*el.cfg, el.name, el.start_line, el.file);
276  validator_->close_tag();
277  }
278 
279  elements.pop();
280  break;
281 
282  default:
283  error(_("Invalid tag name"));
284  }
285 }
286 
287 void parser::parse_variable()
288 {
289  config& cfg = *elements.top().cfg;
290  std::vector<std::string> variables;
291  variables.emplace_back();
292 
293  while(tok_.current_token().type != token::EQUALS) {
294  switch(tok_.current_token().type) {
295  case token::STRING:
296  if(!variables.back().empty()) {
297  variables.back() += ' ';
298  }
299 
300  variables.back() += tok_.current_token().value;
301  break;
302 
303  case token::COMMA:
304  if(variables.back().empty()) {
305  error(_("Empty variable name"));
306  } else {
307  variables.emplace_back();
308  }
309 
310  break;
311 
312  default:
313  error(_("Unexpected characters after variable name (expected , or =)"));
314  break;
315  }
316 
317  tok_.next_token();
318  }
319 
320  if(variables.back().empty()) {
321  error(_("Empty variable name"));
322  }
323 
324  t_string_base buffer;
325 
326  std::vector<std::string>::const_iterator curvar = variables.begin();
327 
328  bool ignore_next_newlines = false, previous_string = false;
329 
330  while(true) {
331  tok_.next_token();
332  assert(curvar != variables.end());
333 
334  switch(tok_.current_token().type) {
335  case token::COMMA:
336  if((curvar + 1) != variables.end()) {
337  if(buffer.translatable()) {
338  cfg[*curvar] = t_string(buffer);
339  } else {
340  cfg[*curvar] = buffer.value();
341  }
342 
343  if(validator_) {
344  validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
345  }
346 
347  buffer = t_string_base();
348  ++curvar;
349  } else {
350  buffer += ",";
351  }
352 
353  break;
354 
355  case token::UNDERSCORE:
356  tok_.next_token();
357 
358  switch(tok_.current_token().type) {
360  error(_("Unterminated quoted string"));
361  break;
362 
363  case token::QSTRING:
364  buffer += t_string_base(tok_.current_token().value, tok_.textdomain());
365  break;
366 
367  default:
368  buffer += "_";
369  buffer += tok_.current_token().value;
370  break;
371 
372  case token::END:
373  case token::NEWLINE:
374  buffer += "_";
375  goto finish;
376  }
377 
378  break;
379 
380  case token::PLUS:
381  ignore_next_newlines = true;
382  continue;
383 
384  case token::STRING:
385  if(previous_string) {
386  buffer += " ";
387  }
388 
389  [[fallthrough]];
390 
391  default:
392  buffer += tok_.current_token().value;
393  break;
394 
395  case token::QSTRING:
396  buffer += tok_.current_token().value;
397  break;
398 
400  error(_("Unterminated quoted string"));
401  break;
402 
403  case token::NEWLINE:
404  if(ignore_next_newlines) {
405  continue;
406  }
407 
408  [[fallthrough]];
409 
410  case token::END:
411  goto finish;
412  }
413 
414  previous_string = tok_.current_token().type == token::STRING;
415  ignore_next_newlines = false;
416  }
417 
418 finish:
419 
420  if(buffer.translatable()) {
421  cfg[*curvar] = t_string(buffer);
422  } else {
423  cfg[*curvar] = buffer.value();
424  }
425 
426  if(validator_) {
427  validator_->validate_key(cfg, *curvar, cfg[*curvar], tok_.get_start_line(), tok_.get_file());
428  }
429 
430  while(++curvar != variables.end()) {
431  cfg[*curvar] = "";
432  }
433 }
434 
435 /**
436  * This function is crap. Don't use it on a string_map with prefixes.
437  */
438 std::string parser::lineno_string(utils::string_map& i18n_symbols,
439  const std::string& lineno,
440  const std::string& error_string,
441  const std::string& hint_string,
442  const std::string& debug_string)
443 {
444  i18n_symbols["pos"] = ::lineno_string(lineno);
445  std::string result = error_string;
446 
447  if(!hint_string.empty()) {
448  result += '\n' + hint_string;
449  }
450 
451  if(!debug_string.empty()) {
452  result += '\n' + debug_string;
453  }
454 
455  for(utils::string_map::value_type& var : i18n_symbols) {
456  boost::algorithm::replace_all(result, std::string("$") + var.first, std::string(var.second));
457  }
458 
459  return result;
460 }
461 
462 void parser::error(const std::string& error_type, const std::string& pos_format)
463 {
464  std::string hint_string = pos_format;
465 
466  if(hint_string.empty()) {
467  hint_string = _("at $pos");
468  }
469 
470  utils::string_map i18n_symbols;
471  i18n_symbols["error"] = error_type;
472 
473  std::stringstream ss;
474  ss << tok_.get_start_line() << " " << tok_.get_file();
475 
476 #ifdef DEBUG_TOKENIZER
477  i18n_symbols["value"] = tok_.current_token().value;
478  i18n_symbols["previous_value"] = tok_.previous_token().value;
479 
480  const std::string tok_state = _("Value: ‘$value’ Previous: ‘$previous_value’");
481 #else
482  const std::string tok_state = "";
483 #endif
484 
485  throw config::error(lineno_string(i18n_symbols, ss.str(), "$error", hint_string, tok_state));
486 }
487 
488 
489 // ==================================================================================
490 // HELPERS FOR WRITE_KEY_VAL
491 // ==================================================================================
492 
493 class write_key_val_visitor
494 #ifdef USING_BOOST_VARIANT
495  : public boost::static_visitor<void>
496 #endif
497 {
498 public:
499  write_key_val_visitor(std::ostream& out, unsigned level, std::string& textdomain, const std::string& key, bool strong_quotes)
500  : out_(out)
501  , level_(level)
502  , textdomain_(textdomain)
503  , key_(key)
504  , strong_quotes_(strong_quotes)
505  {
506  }
507 
508  // Generic visitor just streams "key=value".
509  template<typename T>
510  void operator()(const T& v) const
511  {
512  indent();
513  if constexpr(std::is_arithmetic_v<T>) {
514  // for number values, this has to use the same method as in from_string_verify
515  auto buf = utils::charconv_buffer(v);
516  out_ << key_ << '=' << buf.get_view() << '\n';
517  } else {
518  out_ << key_ << '=' << v << '\n';
519  }
520  }
521 
522  //
523  // Specialized visitors for things that go in quotes:
524  //
525 
526  void operator()(const utils::monostate&) const
527  {
528  // Treat blank values as nonexistent which fits better than treating them as empty strings.
529  }
530 
531  void operator()(const std::string& s) const
532  {
533  indent();
534  if(strong_quotes_) {
535  out_ << key_ << '=' << "<<" << utils::wml_escape_strong(s) << ">>" << '\n';
536  } else {
537  out_ << key_ << '=' << '"' << utils::wml_escape_string(s) << '"' << '\n';
538  }
539  }
540 
541  void operator()(const t_string& s) const;
542 
543 private:
544  void indent() const
545  {
546  for(unsigned i = 0; i < level_; ++i) {
547  out_ << '\t';
548  }
549  }
550 
551  std::ostream& out_;
552  const unsigned level_;
553  std::string& textdomain_;
554  const std::string& key_;
555  bool strong_quotes_;
556 };
557 
558 /**
559  * Writes all the parts of a translatable string.
560  *
561  * @note If the first part is translatable and in the wrong textdomain,
562  * the textdomain change has to happen before the attribute name.
563  * That is the reason for not outputting the key beforehand and
564  * letting this function do it.
565  */
566 void write_key_val_visitor::operator()(const t_string& value) const
567 {
568  bool first = true;
569 
570  for(t_string::walker w(value); !w.eos(); w.next()) {
571  if(!first) {
572  out_ << " +\n";
573  }
574 
575  if(w.translatable() && w.textdomain() != textdomain_) {
576  textdomain_ = w.textdomain();
577  out_ << "#textdomain " << textdomain_ << '\n';
578  }
579 
580  indent();
581 
582  if(first) {
583  out_ << key_ << '=';
584  } else {
585  out_ << '\t';
586  }
587 
588  if(w.translatable()) {
589  out_ << '_';
590  }
591 
592  if(strong_quotes_) {
593  out_ << "<<" << utils::wml_escape_strong(w) << ">>";
594  } else {
595  out_ << '"' << utils::wml_escape_string(w) << '"';
596  }
597  first = false;
598  }
599 
600  out_ << '\n';
601 }
602 
603 } // end anon namespace
604 
605 
606 // ==================================================================================
607 // PUBLIC FUNCTION IMPLEMENTATIONS
608 // ==================================================================================
609 
610 config read(std::istream& in, abstract_validator* validator)
611 {
612  return parser(in, validator)();
613 }
614 
615 config read(const std::string& in, abstract_validator* validator)
616 {
617  std::istringstream ss(in);
618  return parser(ss, validator)();
619 }
620 
621 template<typename Decompressor>
622 config read_compressed(std::istream& file, abstract_validator* validator)
623 {
624  // An empty gzip file seems to confuse boost on MSVC, so return early if this is the case.
625  if(file.peek() == EOF) {
626  return {};
627  }
628 
629  boost::iostreams::filtering_stream<boost::iostreams::input> filter;
630  filter.push(Decompressor());
631  filter.push(file);
632 
633  /* This causes gzip_error (and the corresponding bz2 error, std::ios_base::failure) to be
634  * thrown here. save_index_class::data expects that and config_cache::read_cache and other
635  * functions are also capable of catching.
636  *
637  * Note that parser(cuff, filter,validator)(); -> tokenizer::tokenizer can throw exceptions
638  * too (meaning this function already threw these exceptions before this patch).
639  *
640  * We try to fix https://svn.boost.org/trac/boost/ticket/5237 by not creating empty gz files.
641  */
642  filter.exceptions(filter.exceptions() | std::ios_base::badbit);
643 
644  /*
645  * It sometimes seems the file is not empty but still has no real data.
646  * Filter that case here. The previous test might be no longer required but keep it for now.
647  *
648  * On msvc filter.peek() != EOF does not imply filter.good().
649  * We never create empty compressed gzip files because boosts gzip fails at doing that, but
650  * empty compressed bz2 files are possible.
651  */
652  if(filter.peek() == EOF) {
653  LOG_CF << "Empty compressed file or error at reading a compressed file.";
654  return {};
655  }
656 
657  if(!filter.good()) {
658  LOG_CF << " filter.peek() != EOF but !filter.good()."
659  << "This indicates a malformed gz stream and can make Wesnoth crash.";
660  }
661 
662  return parser(filter, validator)();
663 }
664 
665 /** Might throw a std::ios_base::failure especially a gzip_error. */
666 config read_gz(std::istream& file, abstract_validator* validator)
667 {
668  return read_compressed<boost::iostreams::gzip_decompressor>(file, validator);
669 }
670 
671 /** Might throw a std::ios_base::failure especially bzip2_error. */
672 config read_bz2(std::istream& file, abstract_validator* validator)
673 {
674  return read_compressed<boost::iostreams::bzip2_decompressor>(file, validator);
675 }
676 
677 void write_key_val(std::ostream& out,
678  const std::string& key,
679  const config::attribute_value& value,
680  unsigned level,
681  std::string& textdomain,
682  bool strong_quotes)
683 {
684  value.apply_visitor(write_key_val_visitor(out, level, textdomain, key, strong_quotes));
685 }
686 
687 void write_open_child(std::ostream& out, const std::string& child, unsigned int level)
688 {
689  out << std::string(level, '\t') << '[' << child << "]\n";
690 }
691 
692 void write_close_child(std::ostream& out, const std::string& child, unsigned int level)
693 {
694  out << std::string(level, '\t') << "[/" << child << "]\n";
695 }
696 
697 static void write_internal(const config& cfg, std::ostream& out, std::string& textdomain, std::size_t tab = 0, bool strong_quotes = false)
698 {
699  if(tab > max_recursion_levels) {
700  throw config::error("Too many recursion levels in config write");
701  }
702 
703  for(const auto& [key, value] : cfg.attribute_range()) {
704  if(!config::valid_attribute(key)) {
705  ERR_CF << "Config contains invalid attribute name '" << key << "', skipping...";
706  continue;
707  }
708 
709  write_key_val(out, key, value, tab, textdomain, strong_quotes);
710  }
711 
712  for(const auto [key, item_cfg] : cfg.all_children_view()) {
713  if(!config::valid_tag(key)) {
714  ERR_CF << "Config contains invalid tag name '" << key << "', skipping...";
715  continue;
716  }
717 
718  write_open_child(out, key, tab);
719  write_internal(item_cfg, out, textdomain, tab + 1, strong_quotes);
720  write_close_child(out, key, tab);
721  }
722 }
723 
724 static void write_internal(const configr_of& cfg, std::ostream& out, std::string& textdomain, std::size_t tab = 0, bool strong_quotes = false)
725 {
726  if(tab > max_recursion_levels) {
727  throw config::error("Too many recursion levels in config write");
728  }
729 
730  if(cfg.data_) {
731  write_internal(*cfg.data_, out, textdomain, tab, strong_quotes);
732  }
733 
734  for(const auto& pair : cfg.subtags_) {
735  assert(pair.first && pair.second);
736 
737  if(!config::valid_tag(*pair.first)) {
738  ERR_CF << "Config contains invalid tag name '" << *pair.first << "', skipping...";
739  continue;
740  }
741 
742  write_open_child(out, *pair.first, tab);
743  write_internal(*pair.second, out, textdomain, tab + 1, strong_quotes);
744  write_close_child(out, *pair.first, tab);
745  }
746 }
747 
748 void write(std::ostream& out, const configr_of& cfg, unsigned int level, bool strong_quotes)
749 {
750  std::string textdomain = PACKAGE;
751  write_internal(cfg, out, textdomain, level, strong_quotes);
752 }
753 
754 template<typename Compressor>
755 void write_compressed(std::ostream& out, const configr_of& cfg)
756 {
757  boost::iostreams::filtering_stream<boost::iostreams::output> filter;
758  filter.push(Compressor());
759  filter.push(out);
760 
761  write(filter, cfg);
762 
763  // prevent empty gz files because of https://svn.boost.org/trac/boost/ticket/5237
764  filter << "\n";
765 }
766 
767 void write_gz(std::ostream& out, const configr_of& cfg)
768 {
769  write_compressed<boost::iostreams::gzip_compressor>(out, cfg);
770 }
771 
772 void write_bz2(std::ostream& out, const configr_of& cfg)
773 {
774  write_compressed<boost::iostreams::bzip2_compressor>(out, cfg);
775 }
776 
777 } // namespace io
unsigned in
If equal to search_counter, the node is off the list.
Definition: astarsearch.cpp:70
Used in parsing config file.
Definition: validator.hpp:38
Variant for storing WML attributes.
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
static bool valid_tag(std::string_view name)
Definition: config.cpp:129
config & add_child(std::string_view key)
Definition: config.cpp:436
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
const_attr_itors attribute_range() const
Definition: config.cpp:740
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
static bool valid_attribute(std::string_view name)
Definition: config.cpp:152
Helper class for translatable strings.
Definition: tstring.hpp:27
bool translatable() const
Definition: tstring.hpp:113
const std::string & value() const
Definition: tstring.hpp:117
class responsible for parsing the provided text into tokens and tracking information about the curren...
Definition: tokenizer.hpp:94
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
std::size_t i
Definition: function.cpp:1031
static std::string _(const char *str)
Definition: gettext.hpp:97
static int indent
Definition: log.cpp:88
Standard logging facilities (interface).
Definition: parser.cpp:60
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:610
void write_open_child(std::ostream &out, const std::string &child, unsigned int level)
Definition: parser.cpp:687
void write(std::ostream &out, const configr_of &cfg, unsigned int level, bool strong_quotes)
Definition: parser.cpp:748
config read_compressed(std::istream &file, abstract_validator *validator)
Definition: parser.cpp:622
void write_close_child(std::ostream &out, const std::string &child, unsigned int level)
Definition: parser.cpp:692
config read_bz2(std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially bzip2_error.
Definition: parser.cpp:672
void write_bz2(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:772
config read_gz(std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
Definition: parser.cpp:666
void write_compressed(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:755
void write_gz(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:767
void write_key_val(std::ostream &out, const std::string &key, const config::attribute_value &value, unsigned level, std::string &textdomain, bool strong_quotes)
Definition: parser.cpp:677
static void write_internal(const config &cfg, std::ostream &out, std::string &textdomain, std::size_t tab=0, bool strong_quotes=false)
Definition: parser.cpp:697
constexpr auto filter
Definition: ranges.hpp:42
std::string wml_escape_strong(const std::string &str)
Format str as a strongly quoted WML value.
std::map< std::string, t_string > string_map
std::string wml_escape_string(std::string_view str)
Format str as a WML value
int w
Definition: pathfind.cpp:188
std::string lineno_string(const std::string &lineno)
constexpr std::size_t max_recursion_levels
Definition: parser.cpp:57
#define LOG_CF
Definition: parser.cpp:55
#define ERR_CF
Definition: parser.cpp:53
static lg::log_domain log_config("config")
@ SLASH
Definition: tokenizer.hpp:62
@ QSTRING
quoted string, contained within double quotes or by less than/greater than symbols
Definition: tokenizer.hpp:51
@ COMMA
Definition: tokenizer.hpp:60
@ PLUS
Definition: tokenizer.hpp:61
@ CLOSE_BRACKET
Definition: tokenizer.hpp:64
@ UNTERMINATED_QSTRING
reached end of file without finding the closing character for a QSTRING
Definition: tokenizer.hpp:53
@ NEWLINE
Definition: tokenizer.hpp:58
@ EQUALS
Definition: tokenizer.hpp:59
@ UNDERSCORE
Definition: tokenizer.hpp:65
@ OPEN_BRACKET
Definition: tokenizer.hpp:63
@ END
set when EOF is returned by the input stream
Definition: tokenizer.hpp:68
@ STRING
unquoted text
Definition: tokenizer.hpp:49
mock_char c
static map_location::direction s
This file contains information about validation abstract level interface.
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
#define PACKAGE
Definition: wesconfig.h:23