The Battle for Wesnoth  1.19.5+dev
context_free_grammar_generator.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2024
3  by Ján Dugáček
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Algorithm to generate names using a context-free grammar, which allows more control
19  * than the usual Markov chain generator
20  */
21 
23 
24 #include "log.hpp"
25 #include "random.hpp"
26 
27 #include <algorithm>
28 
29 #include <boost/algorithm/string.hpp>
30 
31 static lg::log_domain log_wml("wml");
32 #define ERR_WML LOG_STREAM(err, log_wml)
33 
35 {
36 }
37 
39 {
40  const char* reading = source.c_str();
41  nonterminal* current = nullptr;
42  std::vector<std::string>* filled = nullptr;
43  std::string buf;
44 
45  while (*reading != 0) {
46  if (*reading == '=') {
47  // Leading and trailing whitespace is not significant, but internal whitespace is
48  std::string key = boost::trim_copy(buf);
49  if(key == "!" || key =="(" || key == ")") {
50  throw name_generator_invalid_exception("[context_free_grammar_generator] Parsing error: nonterminals (, ! and ) may not be overridden");
51  }
52  current = &nonterminals_[key];
53  current->possibilities_.emplace_back();
54  filled = &current->possibilities_.back();
55  buf.clear();
56  } else if (*reading == '\n') {
57  // All whitespace is significant after the =
58  if (filled) filled->push_back(buf);
59  filled = nullptr;
60  current = nullptr;
61  buf.clear();
62  } else if (*reading == '|') {
63  if (!filled || !current) {
64  throw name_generator_invalid_exception("[context_free_grammar_generator] Parsing error: misplaced | symbol");
65  }
66  filled->push_back(buf);
67  current->possibilities_.emplace_back();
68  filled = &current->possibilities_.back();
69  buf.clear();
70  } else if (*reading == '\\' && reading[1] == 'n') {
71  reading++;
72  buf.push_back('\n');
73  } else if (*reading == '\\' && reading[1] == 't') {
74  reading++;
75  buf.push_back('\t');
76  } else {
77  if (*reading == '{') {
78  if (!filled) {
79  throw name_generator_invalid_exception("[context_free_grammar_generator] Parsing error: misplaced { symbol");
80  }
81  filled->push_back(buf);
82  buf.clear();
83  }
84  else if (*reading == '}') {
85  if (!filled) {
86  throw name_generator_invalid_exception("[context_free_grammar_generator] Parsing error: misplaced } symbol");
87  }
88  filled->push_back('{' + boost::trim_copy(buf));
89  buf.clear();
90  } else buf.push_back(*reading);
91  }
92  reading++;
93  }
94  if (filled) filled->push_back(buf);
95 }
96 
97 context_free_grammar_generator::context_free_grammar_generator(const std::map<std::string, std::vector<std::string>>& source)
98 {
99  for(auto rule : source) {
100  std::string key = rule.first; // Need to do this because utils::strip is mutating
101  boost::trim(key);
102  if(key == "!" || key =="(" || key == ")") {
103  throw name_generator_invalid_exception("[context_free_grammar_generator] Parsing error: nonterminals (, ! and ) may not be overridden");
104  }
105  for(std::string str : rule.second) {
106  nonterminals_[key].possibilities_.emplace_back();
107  std::vector<std::string>* filled = &nonterminals_[key].possibilities_.back();
108  std::string buf;
109  // A little code duplication here...
110  for(char c : str) {
111  if (c == '{') {
112  if (!filled) {
113  throw name_generator_invalid_exception("[context_free_grammar_generator] Parsing error: misplaced { symbol");
114  }
115  filled->push_back(buf);
116  buf.clear();
117  }
118  else if (c == '}') {
119  if (!filled) {
120  throw name_generator_invalid_exception("[context_free_grammar_generator] Parsing error: misplaced } symbol");
121  }
122 
123  filled->push_back('{' + boost::trim_copy(buf));
124  buf.clear();
125  } else buf.push_back(c);
126  }
127  if(!buf.empty()) {
128  filled->push_back(buf);
129  }
130  }
131  }
132 }
133 
134 std::string context_free_grammar_generator::print_nonterminal(const std::string& name, uint32_t* seed, short seed_pos) const {
135  if (name == "!") {
136  return "|";
137  }
138  else if (name == "(" ) {
139  return "{";
140  }
141  else if (name == ")" ) {
142  return "}";
143  }
144  else {
145  std::string result = "";
146 
147  std::map<std::string,nonterminal>::const_iterator found = nonterminals_.find(name);
148  if (found == nonterminals_.end()) {
149  lg::log_to_chat() << "[context_free_grammar_generator] Warning: needed nonterminal " << name << " not defined\n";
150  ERR_WML << "[context_free_grammar_generator] Warning: needed nonterminal " << name << " not defined";
151  return "!" + name;
152  }
153  const context_free_grammar_generator::nonterminal& got = found->second;
154  unsigned int picked = seed[seed_pos++] % got.possibilities_.size();
155  if (seed_pos >= seed_size) seed_pos = 0;
156  if (picked == got.last_) {
157  picked = seed[seed_pos++] % got.possibilities_.size();
158  if (seed_pos >= seed_size) seed_pos = 0;
159  }
160  got.last_ = picked;
161  const std::vector<std::string>& used = got.possibilities_[picked];
162  for (unsigned int i = 0; i < used.size(); i++) {
163  if (used[i][0] == '{') result += print_nonterminal(used[i].substr(1), seed, seed_pos);
164  else result += used[i];
165  }
166  return result;
167  }
168 }
169 
171  uint32_t seed[seed_size];
172  init_seed(seed);
173  return print_nonterminal("main", seed, 0);
174 }
175 
176 void context_free_grammar_generator::init_seed(uint32_t seed[]) const {
177  for (unsigned short int i = 0; i < seed_size; i++) {
179  }
180 }
std::string generate() const override
Generates a possible word in the grammar set before.
std::string print_nonterminal(const std::string &name, uint32_t seed[], short int seed_pos) const
std::map< std::string, nonterminal > nonterminals_
context_free_grammar_generator(const std::string &source)
Initialisation.
static const short unsigned int seed_size
uint32_t next_random()
Provides the next random draw.
Definition: random.cpp:84
static lg::log_domain log_wml("wml")
std::size_t i
Definition: function.cpp:1028
Standard logging facilities (interface).
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:543
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
void trim(std::string_view &s)
std::vector< std::vector< std::string > > possibilities_
mock_char c