The Battle for Wesnoth  1.19.10+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 /**
494  * Copies a string fragment and converts it to a suitable format for WML.
495  * (I.e., quotes are doubled.)
496  */
497 std::string escaped_string(const std::string::const_iterator& begin, const std::string::const_iterator& end)
498 {
499  std::string res;
500  std::string::const_iterator iter = begin;
501 
502  while(iter != end) {
503  const char c = *iter;
504  res.append(c == '"' ? 2 : 1, c);
505  ++iter;
506  }
507 
508  return res;
509 }
510 
511 /**
512  * Copies a string and converts it to a suitable format for WML.
513  * (I.e., quotes are doubled.)
514  */
515 inline std::string escaped_string(const std::string& value)
516 {
517  return escaped_string(value.begin(), value.end());
518 }
519 
520 class write_key_val_visitor
521 #ifdef USING_BOOST_VARIANT
522  : public boost::static_visitor<void>
523 #endif
524 {
525 public:
526  write_key_val_visitor(std::ostream& out, unsigned level, std::string& textdomain, const std::string& key)
527  : out_(out)
528  , level_(level)
529  , textdomain_(textdomain)
530  , key_(key)
531  {
532  }
533 
534  // Generic visitor just streams "key=value".
535  template<typename T>
536  void operator()(const T& v) const
537  {
538  indent();
539  if constexpr(std::is_arithmetic_v<T>) {
540  // for number values, this has to use the same method as in from_string_verify
541  auto buf = utils::charconv_buffer(v);
542  out_ << key_ << '=' << buf.get_view() << '\n';
543  } else {
544  out_ << key_ << '=' << v << '\n';
545  }
546  }
547 
548  //
549  // Specialized visitors for things that go in quotes:
550  //
551 
552  void operator()(const utils::monostate&) const
553  {
554  // Treat blank values as nonexistent which fits better than treating them as empty strings.
555  }
556 
557  void operator()(const std::string& s) const
558  {
559  indent();
560  out_ << key_ << '=' << '"' << escaped_string(s) << '"' << '\n';
561  }
562 
563  void operator()(const t_string& s) const;
564 
565 private:
566  void indent() const
567  {
568  for(unsigned i = 0; i < level_; ++i) {
569  out_ << '\t';
570  }
571  }
572 
573  std::ostream& out_;
574  const unsigned level_;
575  std::string& textdomain_;
576  const std::string& key_;
577 };
578 
579 /**
580  * Writes all the parts of a translatable string.
581  *
582  * @note If the first part is translatable and in the wrong textdomain,
583  * the textdomain change has to happen before the attribute name.
584  * That is the reason for not outputting the key beforehand and
585  * letting this function do it.
586  */
587 void write_key_val_visitor::operator()(const t_string& value) const
588 {
589  bool first = true;
590 
591  for(t_string::walker w(value); !w.eos(); w.next()) {
592  if(!first) {
593  out_ << " +\n";
594  }
595 
596  if(w.translatable() && w.textdomain() != textdomain_) {
597  textdomain_ = w.textdomain();
598  out_ << "#textdomain " << textdomain_ << '\n';
599  }
600 
601  indent();
602 
603  if(first) {
604  out_ << key_ << '=';
605  } else {
606  out_ << '\t';
607  }
608 
609  if(w.translatable()) {
610  out_ << '_';
611  }
612 
613  out_ << '"' << escaped_string(w.begin(), w.end()) << '"';
614  first = false;
615  }
616 
617  out_ << '\n';
618 }
619 
620 } // end anon namespace
621 
622 
623 // ==================================================================================
624 // PUBLIC FUNCTION IMPLEMENTATIONS
625 // ==================================================================================
626 
627 config read(std::istream& in, abstract_validator* validator)
628 {
629  return parser(in, validator)();
630 }
631 
632 config read(const std::string& in, abstract_validator* validator)
633 {
634  std::istringstream ss(in);
635  return parser(ss, validator)();
636 }
637 
638 template<typename Decompressor>
639 config read_compressed(std::istream& file, abstract_validator* validator)
640 {
641  // An empty gzip file seems to confuse boost on MSVC, so return early if this is the case.
642  if(file.peek() == EOF) {
643  return {};
644  }
645 
646  boost::iostreams::filtering_stream<boost::iostreams::input> filter;
647  filter.push(Decompressor());
648  filter.push(file);
649 
650  /* This causes gzip_error (and the corresponding bz2 error, std::ios_base::failure) to be
651  * thrown here. save_index_class::data expects that and config_cache::read_cache and other
652  * functions are also capable of catching.
653  *
654  * Note that parser(cuff, filter,validator)(); -> tokenizer::tokenizer can throw exceptions
655  * too (meaning this function already threw these exceptions before this patch).
656  *
657  * We try to fix https://svn.boost.org/trac/boost/ticket/5237 by not creating empty gz files.
658  */
659  filter.exceptions(filter.exceptions() | std::ios_base::badbit);
660 
661  /*
662  * It sometimes seems the file is not empty but still has no real data.
663  * Filter that case here. The previous test might be no longer required but keep it for now.
664  *
665  * On msvc filter.peek() != EOF does not imply filter.good().
666  * We never create empty compressed gzip files because boosts gzip fails at doing that, but
667  * empty compressed bz2 files are possible.
668  */
669  if(filter.peek() == EOF) {
670  LOG_CF << "Empty compressed file or error at reading a compressed file.";
671  return {};
672  }
673 
674  if(!filter.good()) {
675  LOG_CF << " filter.peek() != EOF but !filter.good()."
676  << "This indicates a malformed gz stream and can make Wesnoth crash.";
677  }
678 
679  return parser(filter, validator)();
680 }
681 
682 /** Might throw a std::ios_base::failure especially a gzip_error. */
683 config read_gz(std::istream& file, abstract_validator* validator)
684 {
685  return read_compressed<boost::iostreams::gzip_decompressor>(file, validator);
686 }
687 
688 /** Might throw a std::ios_base::failure especially bzip2_error. */
689 config read_bz2(std::istream& file, abstract_validator* validator)
690 {
691  return read_compressed<boost::iostreams::bzip2_decompressor>(file, validator);
692 }
693 
694 void write_key_val(std::ostream& out,
695  const std::string& key,
696  const config::attribute_value& value,
697  unsigned level,
698  std::string& textdomain)
699 {
700  value.apply_visitor(write_key_val_visitor(out, level, textdomain, key));
701 }
702 
703 void write_open_child(std::ostream& out, const std::string& child, unsigned int level)
704 {
705  out << std::string(level, '\t') << '[' << child << "]\n";
706 }
707 
708 void write_close_child(std::ostream& out, const std::string& child, unsigned int level)
709 {
710  out << std::string(level, '\t') << "[/" << child << "]\n";
711 }
712 
713 static void write_internal(const config& cfg, std::ostream& out, std::string& textdomain, std::size_t tab = 0)
714 {
715  if(tab > max_recursion_levels) {
716  throw config::error("Too many recursion levels in config write");
717  }
718 
719  for(const auto& [key, value] : cfg.attribute_range()) {
720  if(!config::valid_attribute(key)) {
721  ERR_CF << "Config contains invalid attribute name '" << key << "', skipping...";
722  continue;
723  }
724 
725  write_key_val(out, key, value, tab, textdomain);
726  }
727 
728  for(const auto [key, item_cfg] : cfg.all_children_view()) {
729  if(!config::valid_tag(key)) {
730  ERR_CF << "Config contains invalid tag name '" << key << "', skipping...";
731  continue;
732  }
733 
734  write_open_child(out, key, tab);
735  write_internal(item_cfg, out, textdomain, tab + 1);
736  write_close_child(out, key, tab);
737  }
738 }
739 
740 static void write_internal(const configr_of& cfg, std::ostream& out, std::string& textdomain, std::size_t tab = 0)
741 {
742  if(tab > max_recursion_levels) {
743  throw config::error("Too many recursion levels in config write");
744  }
745 
746  if(cfg.data_) {
747  write_internal(*cfg.data_, out, textdomain, tab);
748  }
749 
750  for(const auto& pair : cfg.subtags_) {
751  assert(pair.first && pair.second);
752 
753  if(!config::valid_tag(*pair.first)) {
754  ERR_CF << "Config contains invalid tag name '" << *pair.first << "', skipping...";
755  continue;
756  }
757 
758  write_open_child(out, *pair.first, tab);
759  write_internal(*pair.second, out, textdomain, tab + 1);
760  write_close_child(out, *pair.first, tab);
761  }
762 }
763 
764 void write(std::ostream& out, const configr_of& cfg, unsigned int level)
765 {
766  std::string textdomain = PACKAGE;
767  write_internal(cfg, out, textdomain, level);
768 }
769 
770 template<typename Compressor>
771 void write_compressed(std::ostream& out, const configr_of& cfg)
772 {
773  boost::iostreams::filtering_stream<boost::iostreams::output> filter;
774  filter.push(Compressor());
775  filter.push(out);
776 
777  write(filter, cfg);
778 
779  // prevent empty gz files because of https://svn.boost.org/trac/boost/ticket/5237
780  filter << "\n";
781 }
782 
783 void write_gz(std::ostream& out, const configr_of& cfg)
784 {
785  write_compressed<boost::iostreams::gzip_compressor>(out, cfg);
786 }
787 
788 void write_bz2(std::ostream& out, const configr_of& cfg)
789 {
790  write_compressed<boost::iostreams::bzip2_compressor>(out, cfg);
791 }
792 
793 } // namespace io
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:158
const_attr_itors attribute_range() const
Definition: config.cpp:756
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
static bool valid_tag(config_key_type name)
Definition: config.cpp:129
static bool valid_attribute(config_key_type name)
Definition: config.cpp:152
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
config & add_child(config_key_type key)
Definition: config.cpp:436
Helper class for translatable strings.
Definition: tstring.hpp:27
bool translatable() const
Definition: tstring.hpp:110
const std::string & value() const
Definition: tstring.hpp:114
class responsible for parsing the provided text into tokens and tracking information about the curren...
Definition: tokenizer.hpp:99
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1030
int w
unsigned in
If equal to search_counter, the node is off the list.
static std::string _(const char *str)
Definition: gettext.hpp:97
static int indent
Definition: log.cpp:59
Standard logging facilities (interface).
Definition: parser.cpp:60
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:627
void write_open_child(std::ostream &out, const std::string &child, unsigned int level)
Definition: parser.cpp:703
config read_compressed(std::istream &file, abstract_validator *validator)
Definition: parser.cpp:639
void write_close_child(std::ostream &out, const std::string &child, unsigned int level)
Definition: parser.cpp:708
config read_bz2(std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially bzip2_error.
Definition: parser.cpp:689
void write_bz2(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:788
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:764
config read_gz(std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
Definition: parser.cpp:683
static void write_internal(const config &cfg, std::ostream &out, std::string &textdomain, std::size_t tab=0)
Definition: parser.cpp:713
void write_compressed(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:771
void write_gz(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:783
void write_key_val(std::ostream &out, const std::string &key, const config::attribute_value &value, unsigned level, std::string &textdomain)
Definition: parser.cpp:694
constexpr auto filter
Definition: ranges.hpp:38
std::map< std::string, t_string > string_map
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")
std::vector< std::pair< const std::string *, const configr_of * > > subtags_
const config * data_
@ SLASH
Definition: tokenizer.hpp:67
@ QSTRING
quoted string, contained within double quotes or by less than/greater than symbols
Definition: tokenizer.hpp:56
@ COMMA
Definition: tokenizer.hpp:65
@ PLUS
Definition: tokenizer.hpp:66
@ CLOSE_BRACKET
Definition: tokenizer.hpp:69
@ UNTERMINATED_QSTRING
reached end of file without finding the closing character for a QSTRING
Definition: tokenizer.hpp:58
@ NEWLINE
Definition: tokenizer.hpp:63
@ EQUALS
Definition: tokenizer.hpp:64
@ UNDERSCORE
Definition: tokenizer.hpp:70
@ OPEN_BRACKET
Definition: tokenizer.hpp:68
@ END
set when EOF is returned by the input stream
Definition: tokenizer.hpp:73
@ STRING
unquoted text
Definition: tokenizer.hpp:54
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