The Battle for Wesnoth  1.19.10+dev
test_formula_core.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-test"
16 
17 #include <boost/test/unit_test.hpp>
18 
19 #include <array>
20 
21 #include "formula/formula.hpp"
22 #include "formula/callable.hpp"
23 #include "formula/tokenizer.hpp"
24 
25 using namespace wfl;
26 
27 class mock_char : public formula_callable {
28  variant get_value(const std::string& key) const {
29  if(key == "strength") {
30  return variant(15);
31  } else if(key == "agility") {
32  return variant(12);
33  }
34 
35  return variant(10);
36  }
37 };
38 
39 class mock_party : public formula_callable {
40  variant get_value(const std::string& key) const {
41  if(key == "members") {
42  i_[0].add("strength",variant(12));
43  i_[1].add("strength",variant(16));
44  i_[2].add("strength",variant(14));
45  std::vector<variant> members;
46  for(int n = 0; n != 3; ++n) {
47  members.emplace_back(i_[n].fake_ptr());
48  }
49 
50  return variant(members);
51  } else if(key == "char") {
52  return variant(c_.fake_ptr());
53  } else {
54  return variant(0);
55  }
56  }
57 
59  mutable map_formula_callable i_[3];
60 
61 public:
63 };
64 
65 BOOST_AUTO_TEST_SUITE(formula_core)
66 
69 
70 BOOST_AUTO_TEST_CASE(test_formula_basic_arithmetic)
71 {
72  BOOST_CHECK_EQUAL(formula("strength").evaluate(c).as_int(), 15);
73  BOOST_CHECK_EQUAL(formula("17").evaluate().as_int(), 17);
74 
75  BOOST_CHECK_EQUAL(formula("strength/2 + agility").evaluate(c).as_int(), 19);
76  BOOST_CHECK_EQUAL(formula("(strength+agility)/2").evaluate(c).as_int(), 13);
77 
78  BOOST_CHECK_EQUAL(formula("20 % 3").evaluate().as_int(), 2);
79  BOOST_CHECK_EQUAL(formula("19.5 % 3").evaluate().as_decimal(),
80  static_cast<int>(1000.0 * 1.5));
81 
82  BOOST_CHECK_EQUAL(formula("-5").evaluate().as_int(), -5);
83 
84  BOOST_CHECK_EQUAL(formula("4^2").evaluate().as_int(), 16);
85  BOOST_CHECK_EQUAL(formula("2+3^3").evaluate().as_int(), 29);
86  BOOST_CHECK_EQUAL(formula("2*3^3+2").evaluate().as_int(), 56);
87  BOOST_CHECK_EQUAL(formula("2^3^2").evaluate().as_int(), 512);
88  BOOST_CHECK_EQUAL(formula("9^3").evaluate().as_int(), 729);
89  BOOST_CHECK(formula("(-2)^0.5").evaluate().is_null());
90 }
91 
92 BOOST_AUTO_TEST_CASE(test_formula_basic_logic)
93 {
94  BOOST_CHECK_EQUAL(formula("strength > 12").evaluate(c).as_int(), 1);
95  BOOST_CHECK_EQUAL(formula("strength > 18").evaluate(c).as_int(), 0);
96 
97  BOOST_CHECK_EQUAL(formula("if(strength > 12, 7, 2)").evaluate(c).as_int(), 7);
98  BOOST_CHECK_EQUAL(formula("if(strength > 18, 7, 2)").evaluate(c).as_int(), 2);
99 
100  BOOST_CHECK_EQUAL(formula("2 and 1").evaluate().as_int(), 1);
101  BOOST_CHECK_EQUAL(formula("2 and 0").evaluate().as_int(), 0);
102  BOOST_CHECK_EQUAL(formula("2 or 0").evaluate().as_int(), 2);
103 
104  BOOST_CHECK_EQUAL(formula("not 5").evaluate().as_int(), 0);
105  BOOST_CHECK_EQUAL(formula("not 0").evaluate().as_int(), 1);
106 }
107 
108 BOOST_AUTO_TEST_CASE(test_formula_callable)
109 {
110  // These are just misc tests that were in the original unit tests
111  // I wasn't sure how to classify them.
112  BOOST_CHECK_EQUAL(formula("char.strength").evaluate(p).as_int(), 15);
113  BOOST_CHECK_EQUAL(formula("choose(members,strength).strength").evaluate(p).as_int(), 16);
114 
115  BOOST_CHECK_EQUAL(formula("char.sum([strength, agility, intelligence])").evaluate(p).as_int(), 37);
116 }
117 
118 BOOST_AUTO_TEST_CASE(test_formula_where_clause)
119 {
120  BOOST_CHECK_EQUAL(formula("x*5 where x=1").evaluate().as_int(), 5);
121  BOOST_CHECK_EQUAL(formula("x*5 where x=2").evaluate().as_int(), 10);
122 
123  BOOST_CHECK_EQUAL(formula("x*(a*b where a=2,b=1) where x=5").evaluate().as_int(), 10);
124  BOOST_CHECK_EQUAL(formula("char.strength * ability where ability=3").evaluate(p).as_int(), 45);
125 }
126 
127 BOOST_AUTO_TEST_CASE(test_formula_strings)
128 {
129  BOOST_CHECK_EQUAL(formula("'abcd' = 'abcd'").evaluate().as_bool(), true);
130  BOOST_CHECK_EQUAL(formula("'abcd' = 'acd'").evaluate().as_bool(), false);
131 
132  BOOST_CHECK_EQUAL(formula("'ab' .. 'cd'").evaluate().as_string(), "abcd");
133 
134  BOOST_CHECK_EQUAL(formula("'strength, agility: [strength], [agility]'").evaluate(c).as_string(),
135  "strength, agility: 15, 12");
136 
137  BOOST_CHECK_EQUAL(formula("'String with [']quotes['] and [(]brackets[)]!'").evaluate().as_string(),
138  "String with 'quotes' and [brackets]!");
139  BOOST_CHECK_EQUAL(formula("'String with ['embedded ' .. 'string']!'").evaluate().as_string(),
140  "String with embedded string!");
141 }
142 
143 BOOST_AUTO_TEST_CASE(test_formula_dice) {
144  const int dice_roll = formula("3d6").evaluate().as_int();
145  assert(dice_roll >= 3 && dice_roll <= 18);
146 }
147 
148 BOOST_AUTO_TEST_CASE(test_formula_containers) {
149  variant myarray = formula("[1,2,3]").evaluate();
150  BOOST_CHECK_EQUAL(myarray.num_elements(), 3);
151  BOOST_CHECK_EQUAL(myarray[0].as_int(), 1);
152  BOOST_CHECK_EQUAL(myarray[1].as_int(), 2);
153  BOOST_CHECK_EQUAL(myarray[2].as_int(), 3);
154 
155  variant mydict = formula("['foo' -> 5, 'bar' ->7]").evaluate();
156  BOOST_CHECK_EQUAL(mydict.num_elements(), 2);
157  BOOST_CHECK_EQUAL(mydict[variant("foo")].as_int(), 5);
158  BOOST_CHECK_EQUAL(mydict[variant("bar")].as_int(), 7);
159 
160  variant myrange = formula("-2~2").evaluate();
161  BOOST_CHECK_EQUAL(myrange.num_elements(), 5);
162  BOOST_CHECK_EQUAL(myrange[0].as_int(), -2);
163  BOOST_CHECK_EQUAL(myrange[1].as_int(), -1);
164  BOOST_CHECK_EQUAL(myrange[2].as_int(), 0);
165  BOOST_CHECK_EQUAL(myrange[3].as_int(), 1);
166  BOOST_CHECK_EQUAL(myrange[4].as_int(), 2);
167 
168  variant myslice = formula("(10~20)[[1,3,7,9]]").evaluate();
169  BOOST_CHECK_EQUAL(myslice.num_elements(), 4);
170  BOOST_CHECK_EQUAL(myslice[0].as_int(), 11);
171  BOOST_CHECK_EQUAL(myslice[1].as_int(), 13);
172  BOOST_CHECK_EQUAL(myslice[2].as_int(), 17);
173  BOOST_CHECK_EQUAL(myslice[3].as_int(), 19);
174 }
175 
176 BOOST_AUTO_TEST_CASE(test_formula_tokenizer) {
177  using namespace wfl::tokenizer;
178  const std::string test = "[(abc + 4 * (5+3))^2.0, functions, '[']thing[']']";
179  auto i1 = test.begin();
180  auto i2 = test.end();
181  const std::array<std::pair<std::string, token_type>, 25> tokens {{
182  {"[", token_type::lsquare},
183  {"(", token_type::lparens},
184  {"abc", token_type::identifier},
185  {" ", token_type::whitespace},
187  {" ", token_type::whitespace},
188  {"4", token_type::integer},
189  {" ", token_type::whitespace},
191  {" ", token_type::whitespace},
192  {"(", token_type::lparens},
193  {"5", token_type::integer},
195  {"3", token_type::integer},
196  {")", token_type::rparens},
197  {")", token_type::rparens},
199  {"2.0", token_type::decimal},
200  {",", token_type::comma},
201  {" ", token_type::whitespace},
202  {"functions", token_type::keyword},
203  {",", token_type::comma},
204  {" ", token_type::whitespace},
205  {"'[']thing[']'", token_type::string_literal},
206  {"]", token_type::rsquare},
207  }};
208  for(const auto& [str, type] : tokens) {
209  const token t = get_token(i1, i2);
210  assert(std::string(t.begin, t.end) == str);
211  assert(t.type == type);
212  }
213 }
214 
215 BOOST_AUTO_TEST_SUITE_END()
double t
Definition: astarsearch.cpp:63
variant get_value(const std::string &key) const
variant get_value(const std::string &key) const
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:48
int as_int() const
Definition: variant.cpp:291
std::size_t num_elements() const
Definition: variant.cpp:267
token get_token(iterator &i1, const iterator i2)
Definition: tokenizer.cpp:44
Definition: contexts.hpp:43
BOOST_AUTO_TEST_SUITE(filesystem)
mock_char c
BOOST_AUTO_TEST_CASE(test_formula_basic_arithmetic)
mock_party p
static map_location::direction n