The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
variant.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
3 by David White <dave@whitevine.net>
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#include <cassert>
17#include <cmath>
18#include <cstring>
19#include <memory>
20#include "utils/span.hpp"
21
22#include "formatter.hpp"
23#include "formula/function.hpp"
25#include "log.hpp"
26
27static lg::log_domain log_scripting_formula("scripting/formula");
28#define DBG_SF LOG_STREAM(debug, log_scripting_formula)
29#define LOG_SF LOG_STREAM(info, log_scripting_formula)
30#define WRN_SF LOG_STREAM(warn, log_scripting_formula)
31#define ERR_SF LOG_STREAM(err, log_scripting_formula)
32
33
34namespace wfl
35{
36namespace
37{
38// Static value to initialize null variants to ensure its value is never nullptr.
39const auto null_value = std::make_shared<variant_value_base>();
40
41std::string get_debug_description(const variant& v)
42{
43 return formatter{} << formula_variant::get_string(v.type()) << " (" + v.to_debug_string() << ")";
44}
45
46// Small helper function to get a standard type error message.
47std::string was_expecting(const std::string& expected, const std::string& description)
48{
49 return formatter{} << "TYPE ERROR: expected " << expected << " but found " << description;
50}
51
52// Small helper function to get a standard type error message.
53std::string was_expecting(const std::string& expected, const variant& v)
54{
55 return was_expecting(expected, get_debug_description(v));
56}
57
58// Throws a type_error if both variants are not of the given type
59void must_both_be(formula_variant::type t, const variant& v1, const variant& v2)
60{
61 if(v1.type() != t || v2.type() != t) {
62 const std::string expected = formatter{}
65
66 const std::string provided = formatter{}
67 << get_debug_description(v1) << ", "
68 << get_debug_description(v2);
69
70 throw type_error{was_expecting(expected, provided)};
71 }
72}
73
74} // namespace
75
76template<typename T>
77std::shared_ptr<T> value_cast(const variant& v)
78{
79 auto res = std::dynamic_pointer_cast<T>(v.value_);
80 if(!res) {
81 throw type_error{was_expecting(formula_variant::get_string(T::value_type), v)};
82 }
83
84 return res;
85}
86
87type_error::type_error(const std::string& str) : game::error(str)
88{
89 PLAIN_LOG << "ERROR: " << message << "\n" << call_stack_manager::get();
90}
91
93 : type_(formula_variant::type::null)
94 , container_(nullptr)
95 , iter_()
96{
97}
98
100 : type_(value->get_type())
101 , container_(value)
102 , iter_(iter)
103{
104}
105
107{
108 if(!container_) {
109 return variant();
110 }
111
113}
114
116{
117 if(container_) {
119 }
120
121 return *this;
122}
123
125{
126 variant_iterator temp(*this);
127 if(container_) {
129 }
130
131 return temp;
132}
133
135{
136 if(container_) {
138 }
139
140 return *this;
141}
142
144{
145 variant_iterator temp(*this);
146 if(container_) {
148 }
149
150 return temp;
151}
152
154{
155 if(!container_ && !that.container_) {
156 return true;
157 }
158
159 if(container_ == that.container_) {
160 return container_->iterator_equals(iter_, that.iter_);
161 }
162
163 return false;
164}
165
167{
168 return !operator==(that);
169}
170
171
173 : value_(null_value)
174{}
175
177 : value_(std::make_shared<variant_int>(n))
178{
179}
180
185
190
191variant::variant(const std::vector<variant>& vec)
192 : value_(std::make_shared<variant_list>(vec))
193{
194}
195
196variant::variant(std::vector<variant>&& vec)
197 : value_(std::make_shared<variant_list>(std::move(vec)))
198{
199}
200
201variant::variant(const std::string& str)
202 : value_(std::make_shared<variant_string>(str))
203{
204}
205
206variant::variant(std::string&& str)
207 : value_(std::make_shared<variant_string>(std::move(str)))
208{
209}
210
211variant::variant(const std::map<variant, variant>& map)
212 : value_(std::make_shared<variant_map>(map))
213{
214}
215
216variant::variant(std::map<variant, variant>&& map)
217 : value_(std::make_shared<variant_map>(std::move(map)))
218{
219}
220
222 : value_(std::make_shared<variant_callable>(std::move(callable)))
223{
224}
225
226formula_variant::type variant::type() const
227{
228 return value_->get_type();
229}
230
231variant variant::operator[](std::size_t n) const
232{
233 if(is_callable()) {
234 return *this;
235 }
236
237 try {
238 return as_list().at(n);
239 } catch(const std::out_of_range&) {
240 throw type_error("invalid index");
241 }
242}
243
245{
246 if(is_callable()) {
247 return *this;
248 }
249
250 if(is_map()) {
251 auto& map = as_map();
252
253 auto i = map.find(v);
254 if(i == map.end()) {
255 return variant();
256 }
257
258 return i->second;
259 } else if(is_list()) {
260 if(v.is_list()) {
261 std::vector<variant> slice;
262 for(std::size_t i = 0; i < v.num_elements(); ++i) {
263 slice.push_back((*this)[v[i]]);
264 }
265
266 return variant(std::move(slice));
267 } else if(v.as_int() < 0) {
268 return operator[](num_elements() + v.as_int());
269 }
270
271 return operator[](v.as_int());
272 }
273
274 throw type_error(was_expecting("a list or a map", *this));
275}
276
278{
279 std::vector<variant> tmp;
280 for(const auto& [key, value] : as_map()) {
281 tmp.push_back(key);
282 }
283
284 return variant(std::move(tmp));
285}
286
288{
289 std::vector<variant> tmp;
290 for(const auto& [key, value] : as_map()) {
291 tmp.push_back(value);
292 }
293
294 return variant(std::move(tmp));
295}
296
298{
299 return value_->make_iterator().begin();
300}
301
303{
304 return value_->make_iterator().end();
305}
306
308{
309 return value_->is_empty();
310}
311
312std::size_t variant::num_elements() const
313{
314 if(!is_list() && !is_map()) {
315 throw type_error(was_expecting("a list or a map", *this));
316 }
317
318 return value_->num_elements();
319}
320
321variant variant::get_member(const std::string& name) const
322{
323 if(is_callable()) {
324 if(auto obj = as_callable()) {
325 return obj->query_value(name);
326 }
327 }
328
329 if(name == "self") {
330 return *this;
331 }
332
333 return variant();
334}
335
336int variant::as_int(int fallback) const
337{
338 if(is_null()) { return fallback; }
339 if(is_decimal()) { return as_decimal() / 1000; }
340
341 return value_cast<variant_int>(*this)->get_numeric_value();
342}
343
344int variant::as_decimal(int fallback) const
345{
346 if(is_decimal()) {
347 return value_cast<variant_decimal>(*this)->get_numeric_value();
348 } else if(is_int()) {
349 return value_cast<variant_int>(*this)->get_numeric_value() * 1000;
350 } else if(is_null()) {
351 return fallback;
352 }
353
354 throw type_error(was_expecting("an integer or a decimal", *this));
355}
356
358{
359 return value_->as_bool();
360}
361
362const std::string& variant::as_string() const
363{
364 return value_cast<variant_string>(*this)->get_string();
365}
366
367const std::vector<variant>& variant::as_list() const
368{
369 return value_cast<variant_list>(*this)->get_container();
370}
371
372const std::map<variant, variant>& variant::as_map() const
373{
374 return value_cast<variant_map>(*this)->get_container();
375}
376
378{
379 return value_cast<variant_callable>(*this)->get_callable();
380}
381
383{
384 if(is_list() && v.is_list()) {
385 return concatenate(v);
386 }
387
388 if(is_map() && v.is_map()) {
389 std::map<variant, variant> res = as_map();
390
391 for(const auto& member : v.as_map()) {
392 res[member.first] = member.second;
393 }
394
395 return variant(std::move(res));
396 }
397
398 if(is_decimal() || v.is_decimal()) {
400 }
401
402 return variant(as_int() + v.as_int());
403}
404
406{
407 if(is_decimal() || v.is_decimal()) {
409 }
410
411 return variant(as_int() - v.as_int());
412}
413
415{
416 if(is_decimal() || v.is_decimal()) {
417
418 long long long_int = as_decimal();
419
420 long_int *= v.as_decimal();
421
422 long_int /= 100;
423
424 if(long_int%10 >= 5) {
425 long_int /= 10;
426 ++long_int;
427 } else {
428 long_int/=10;
429 }
430
431 return variant(static_cast<int>(long_int) , DECIMAL_VARIANT );
432 }
433
434 return variant(as_int() * v.as_int());
435}
436
438{
439 if(is_decimal() || v.is_decimal()) {
440 int denominator = v.as_decimal();
441
442 if(denominator == 0) {
443 throw type_error("decimal divide by zero error");
444 }
445
446 long long long_int = as_decimal();
447
448 long_int *= 10000;
449
450 long_int /= denominator;
451
452 if(long_int%10 >= 5) {
453 long_int /= 10;
454 ++long_int;
455 } else {
456 long_int/=10;
457 }
458
459 return variant(static_cast<int>(long_int), DECIMAL_VARIANT);
460 }
461
462 const int numerator = as_int();
463 const int denominator = v.as_int();
464
465 if(denominator == 0) {
466 throw type_error("int divide by zero error");
467 }
468
469 return variant(numerator / denominator);
470}
471
473{
474 if(is_decimal() || v.is_decimal()) {
475 const int numerator = as_decimal();
476 const int denominator = v.as_decimal();
477 if(denominator == 0) {
478 throw type_error("divide by zero error");
479 }
480
481 return variant(numerator % denominator, DECIMAL_VARIANT);
482 } else {
483 const int numerator = as_int();
484 const int denominator = v.as_int();
485 if(denominator == 0) {
486 throw type_error("divide by zero error");
487 }
488
489 return variant(numerator % denominator);
490 }
491}
492
494{
495 if(is_decimal() || v.is_decimal()) {
496
497 double res = std::pow(as_decimal() / 1000.0 , v.as_decimal() / 1000.0);
498
499 if(std::isnan(res)) {
500 return variant();
501 }
502
503 return variant(res, DECIMAL_VARIANT);
504 }
505
506 return variant(static_cast<int>(std::round(std::pow(static_cast<double>(as_int()), v.as_int()))));
507}
508
510{
511 if(is_decimal()) {
513 }
514
515 return variant(-as_int());
516}
517
518bool variant::operator==(const variant& v) const
519{
520 if(type() != v.type()) {
521 if(is_decimal() || v.is_decimal()) {
522 return as_decimal() == v.as_decimal();
523 }
524
525 return false;
526 }
527
528 return value_->equals(*v.value_);
529}
530
531bool variant::operator!=(const variant& v) const
532{
533 return !operator==(v);
534}
535
536bool variant::operator<(const variant& v) const
537{
538 if(type() != v.type()) {
539 if(is_decimal() && v.is_int()) {
540 return as_decimal() < v.as_decimal();
541 }
542
543 if(v.is_decimal() && is_int()) {
544 return as_decimal() < v.as_decimal();
545 }
546
547 return type() < v.type();
548 }
549
550 return value_->less_than(*v.value_);
551}
552
553bool variant::operator>=(const variant& v) const
554{
555 return !(*this < v);
556}
557
558bool variant::operator<=(const variant& v) const
559{
560 return !(v < *this);
561}
562
563bool variant::operator>(const variant& v) const
564{
565 return v < *this;
566}
567
569{
570/**
571 * Applies the provided function to the corresponding variants in both lists.
572 *
573 * @todo Expose this to the public API.
574 */
575template<typename Func>
576variant zip_transform(const variant& v1, const variant& v2, const Func& op_func)
577{
578 must_both_be(formula_variant::type::list, v1, v2);
579
580 const std::vector<variant>& lhs = v1.as_list();
581 const std::vector<variant>& rhs = v2.as_list();
582
583 if(lhs.size() != rhs.size()) {
584 throw type_error("zip_transform requires two lists of the same length");
585 }
586
587 std::vector<variant> res;
588 res.reserve(lhs.size());
589
590 for(std::size_t i = 0; i < lhs.size(); ++i) {
591 res.push_back(std::invoke(op_func, lhs[i], rhs[i]));
592 }
593
594 return variant(std::move(res));
595}
596
597static variant concat_lists(const variant& v1, const variant& v2)
598{
599 must_both_be(formula_variant::type::list, v1, v2);
600
601 const std::vector<variant>& lhs = v1.as_list();
602 const std::vector<variant>& rhs = v2.as_list();
603
604 std::vector<variant> res;
605 res.reserve(lhs.size() + rhs.size());
606
607 std::copy(lhs.begin(), lhs.end(), std::back_inserter(res));
608 std::copy(rhs.begin(), rhs.end(), std::back_inserter(res));
609
610 return variant(std::move(res));
611}
612
613} // namespace implementation
614
616{
617 return implementation::zip_transform(*this, v, std::plus<variant>{});
618}
619
621{
622 return implementation::zip_transform(*this, v, std::minus<variant>{});
623}
624
626{
627 return implementation::zip_transform(*this, v, std::multiplies<variant>{});
628}
629
631{
632 return implementation::zip_transform(*this, v, std::divides<variant>{});
633}
634
636{
637 if(is_list() && v.is_list()) {
638 return implementation::concat_lists(*this, v);
639 }
640
641 if(is_string() && v.is_string()) {
642 return variant(as_string() + v.as_string());
643 }
644
645 throw type_error(was_expecting("a list or a string", *this));
646}
647
649{
650 must_both_be(formula_variant::type::integer, *this, v);
651 return value_cast<variant_int>(*this)->build_range_variant(v.as_int());
652}
653
654bool variant::contains(const variant& v) const
655{
656 switch(type()) {
657 case formula_variant::type::list:
658 return utils::contains(as_list(), v);
659 case formula_variant::type::map:
660 return utils::contains(as_map(), v);
661 default:
662 throw type_error(was_expecting("a list or a map", *this));
663 }
664}
665
667{
668 return value_->get_serialized_string();
669}
670
671void variant::serialize_from_string(const std::string& str)
672{
673 try {
674 *this = formula(str).evaluate();
675 } catch(...) {
676 DBG_SF << "Evaluation failed with exception: " << utils::get_unknown_exception_type();
677 *this = variant(str);
678 }
679}
680
681std::string variant::string_cast() const
682{
683 return value_->string_cast();
684}
685
686std::string variant::to_debug_string(bool verbose, formula_seen_stack* seen) const
687{
688 if(!seen) {
689 formula_seen_stack seen_stack;
690 return value_->get_debug_string(seen_stack, verbose);
691 }
692
693 return value_->get_debug_string(*seen, verbose);
694}
695
696variant execute_actions(const variant& execute, const variant& context)
697{
698 // If we don't have a list, try and execute the input variant itself.
699 const auto to_execute = execute.is_list()
700 ? utils::span{execute.as_list()}
701 : utils::span{&execute, 1};
702
703 std::vector<variant> res;
704 res.reserve(to_execute.size());
705
706 for(const variant& v : to_execute) {
707 if(v.is_null()) {
708 continue;
709 }
710
711 auto action = callable_cast<action_callable*>(v);
712 if(!action) {
713 WRN_SF << "Could not execute non-action_callable variant: " << v.to_debug_string();
714 continue;
715 }
716
717 res.push_back(std::const_pointer_cast<action_callable>(action)->execute_self(context));
718 }
719
720 return variant(std::move(res));
721}
722
723}
double t
std::unique_ptr< PangoAttribute, void(*)(PangoAttribute *)> value_
std::ostringstream wrapper.
Definition formatter.hpp:40
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
Iterator class for the variant.
Definition variant.hpp:160
bool operator!=(const variant_iterator &that) const
Definition variant.cpp:166
const variant_value_base * container_
Definition variant.hpp:192
variant operator*() const
Definition variant.cpp:106
bool operator==(const variant_iterator &that) const
Definition variant.cpp:153
variant_iterator & operator--()
Definition variant.cpp:134
variant_iterator()
Constructor for a no-op iterator.
Definition variant.cpp:92
variant_iterator & operator++()
Definition variant.cpp:115
Base class for all variant types.
virtual bool iterator_equals(const utils::any &, const utils::any &) const
Implements the equality functionality of variant_iterator for a value of this type.
virtual variant deref_iterator(const utils::any &iter) const
Implements the dereference functionality of variant_iterator for a value of this type.
virtual void iterator_dec(utils::any &) const
Implements the decrement functionality of variant_iterator for a value of this type.
virtual void iterator_inc(utils::any &) const
Implements the increment functionality of variant_iterator for a value of this type.
variant operator-() const
Definition variant.cpp:509
bool is_map() const
Definition variant.hpp:75
bool is_list() const
Definition variant.hpp:73
variant operator^(const variant &) const
Definition variant.cpp:493
variant concatenate(const variant &v) const
Definition variant.cpp:635
variant get_keys() const
Definition variant.cpp:277
variant_iterator begin() const
Definition variant.cpp:297
std::shared_ptr< variant_value_base > value_
Definition variant.hpp:143
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition variant.cpp:336
variant operator/(const variant &) const
Definition variant.cpp:437
variant get_values() const
Definition variant.cpp:287
void serialize_from_string(const std::string &str)
Definition variant.cpp:671
int as_decimal(int fallback=0) const
Returns the variant's internal representation of decimal number: ie, 1.234 is represented as 1234.
Definition variant.cpp:344
const_formula_callable_ptr as_callable() const
Definition variant.cpp:377
bool operator>=(const variant &) const
Definition variant.cpp:553
std::size_t num_elements() const
Definition variant.cpp:312
formula_variant::type type() const
Definition variant.cpp:226
bool is_decimal() const
Definition variant.hpp:71
bool operator!=(const variant &) const
Definition variant.cpp:531
variant operator+(const variant &) const
Definition variant.cpp:382
bool is_empty() const
Definition variant.cpp:307
bool contains(const variant &other) const
Definition variant.cpp:654
bool operator==(const variant &) const
Definition variant.cpp:518
bool operator>(const variant &) const
Definition variant.cpp:563
variant operator*(const variant &) const
Definition variant.cpp:414
variant get_member(const std::string &name) const
Definition variant.cpp:321
variant list_elements_mul(const variant &v) const
Definition variant.cpp:625
std::string serialize_to_string() const
Definition variant.cpp:666
variant list_elements_add(const variant &v) const
Definition variant.cpp:615
const std::string & as_string() const
Definition variant.cpp:362
std::string string_cast() const
Definition variant.cpp:681
variant list_elements_div(const variant &v) const
Definition variant.cpp:630
variant_iterator end() const
Definition variant.cpp:302
bool as_bool() const
Returns a boolean state of the variant value.
Definition variant.cpp:357
bool operator<(const variant &) const
Definition variant.cpp:536
variant operator[](std::size_t n) const
Definition variant.cpp:231
bool is_string() const
Definition variant.hpp:74
variant build_range(const variant &v) const
Definition variant.cpp:648
bool is_callable() const
Definition variant.hpp:72
variant operator%(const variant &) const
Definition variant.cpp:472
const std::vector< variant > & as_list() const
Definition variant.cpp:367
bool operator<=(const variant &) const
Definition variant.cpp:558
std::string to_debug_string(bool verbose=false, formula_seen_stack *seen=nullptr) const
Definition variant.cpp:686
const std::map< variant, variant > & as_map() const
Definition variant.cpp:372
bool is_null() const
Functions to test the type of the internal value.
Definition variant.hpp:69
bool is_int() const
Definition variant.hpp:70
variant list_elements_sub(const variant &v) const
Definition variant.cpp:620
#define WRN_SF
Definition function.cpp:40
Standard logging facilities (interface).
#define PLAIN_LOG
Definition log.hpp:296
Contains the implementation details for lexical_cast and shouldn't be used directly.
STL namespace.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition general.cpp:23
variant zip_transform(const variant &v1, const variant &v2, const Func &op_func)
Applies the provided function to the corresponding variants in both lists.
Definition variant.cpp:576
static variant concat_lists(const variant &v1, const variant &v2)
Definition variant.cpp:597
std::vector< const_formula_callable_ptr > formula_seen_stack
std::shared_ptr< const formula_callable > const_formula_callable_ptr
std::shared_ptr< T > value_cast(const variant &v)
Definition variant.cpp:77
variant execute_actions(const variant &execute, const variant &context)
Executes all action_callables in execute using the provided context.
Definition variant.cpp:696
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
std::string message
The base template for associating string values with enum values.
Definition enum_base.hpp:33
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition enum_base.hpp:46
static std::string get()
Definition function.cpp:63
type_error(const std::string &str)
Definition variant.cpp:87
static map_location::direction n
#define DBG_SF
Definition variant.cpp:28
static lg::log_domain log_scripting_formula("scripting/formula")
#define i