The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
context_free_grammar_generator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 - 2025
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
31static lg::log_domain log_wml("wml");
32#define ERR_WML LOG_STREAM(err, log_wml)
33
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
97context_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
134std::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
176void 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
#define ERR_WML
static lg::log_domain log_wml("wml")
Standard logging facilities (interface).
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition log.cpp:550
rng * generator
This generator is automatically synced during synced context.
Definition random.cpp:60
std::vector< std::vector< std::string > > possibilities_
#define i
#define c