diff --git a/changelog b/changelog index 0580cbd77182..126c8e19eceb 100644 --- a/changelog +++ b/changelog @@ -21,6 +21,77 @@ Version 1.13.4+dev: (where "array" is a table whose keys are all integers). This joins the elements of the array with commas and produces a single string value. eg {x = {1,2,3}} is equivalent to {x = "1,2,3"}. + * Wesnoth formula engine: + * Formulas in unit filters can now access nearly all unit attributes + * New syntax features: + * String interpolation syntax. Within a formula string (enclosed in + 'single quotes'), the syntax [some_formula] interpolates the result + of the inner formula into the string. (The simplest use case is + interpolating the values of variables.) + * String can now escape special characters: + ['] single quote, [(] open square bracket, [)] close square bracket + * New 'in' operator which tests if a list contains an item or if a map + contains a key. + * New concatenation operator a..b which works on strings and lists + * New range operator a~b which produces a list of consecutive integers + This can also be used for "list slicing" - eg my_list[3~5] returns + a new list containing elements 3 through 5 of my_list. + * Lists can be used as an index for a list. This is "selection indexing" + and returns a new list with only the elements specified by the indexing + list. + * Function definitions (using the def keyword) are now supported in all + formula contexts, which means that they can be used outside FormulaAI. + However, non-FormulaAI functions are currently local to the formula + that declares them. + * Maps containing string keyss that are valid identifiers can now be + indexed with the dot operator instead of the indexing operator. + * Strings can now be indexed via 'string'.char[n]. Also supported are + 'string'.word[n] and 'string'.item[n] (the latter splits on commas) + * Changes to core functions: + * head() takes an optional argument - if present, a sublist is returned. + * abs(), max(), min() now work on decimal numbers + * reduce() function can specify an optional initial accumulator + This will be returned for an empty list instead of null. + * substring() function can now accept a negative size parameter + This counts backwards from the specified offset + A size of -1 is the same as 1. + * if() can take two arguments; returns null if the condition is false + * debug_print() now shows in console if debug mode is on + * New core functions: + * Trig functions tan, acos, asin, atan have been added. (Sin and cos + existed since at least 1.9 but were undocumented until very recently.) + * Other common math functions - root(), sqrt(), cbrt(), log(), exp() + * hypot(x,y) function calculates sqrt(x*x+y*y) with minimal error + * pi() returning the circle ratio + * tail() - opposite of head() + * reverse() function for strings and lists + * zip() function - converts [[1,2,3],[4,5,6]] to [[1,4],[2,5],[3,6]] + * take_while() function returns items from a list until the first one + that fails a condition + * find_string() locates a substring within a string + * replace() replaces a sequence within a string + * type() function checks the type of a formula result + * distance_between() - this was promoted from FormulaAI to core + * Bugfixes: + * Dice operator is now synced (where possible) + * Modulus (%) operator now works on decimal numbers + * Exponentiation (^) operator is now right-associative + * List/map indexing now has highest precedence of any operator (instead of lowest) + * Fix several math operations returning a very large negative number when + the operation was invalid (for example, (-2) ^ 0.5). + Now they return null instead. + * Formula debugger (accessed with the debug() function): + * Now works again, but is skipped unless in debug mode + * No longer explodes on formulas using less-than + * Some better information and formatting in the execution trace + * You can now step through "where" variable assignments + * Deprecated: + * The fai/faiend keywords are deprecated as part of a move to clearly + define the difference between "Wesnoth Formula Language", the language, + and "FormulaAI", the AI engine that uses the language. For now they + still work, but any future code should use wfl/wflend instead. + Use of the .fai file extension is still fine for FormulaAI code; + for other formula code in a separate file, .wfl is recommended instead. Version 1.13.4: * Language and i18n: diff --git a/data/gui/default/widget/toggle_button_orb.cfg b/data/gui/default/widget/toggle_button_orb.cfg index c85bb6474975..426b84faeee8 100644 --- a/data/gui/default/widget/toggle_button_orb.cfg +++ b/data/gui/default/widget/toggle_button_orb.cfg @@ -9,7 +9,7 @@ y = 0 w = {SIZE} h = {SIZE} - name = "('buttons/misc/orb{STATE}.png" + <<~RC(magenta>{icon})')>> + name = "('buttons/misc/orb{STATE}.png~RC(magenta>[icon])')" [/image] #enddef diff --git a/data/test/scenarios/filter_this_unit.cfg b/data/test/scenarios/filter_this_unit.cfg index 78c6541f7af0..9e48cb8e93ad 100644 --- a/data/test/scenarios/filter_this_unit.cfg +++ b/data/test/scenarios/filter_this_unit.cfg @@ -58,3 +58,33 @@ )} [/event] )} + +{GENERIC_UNIT_TEST filter_fai_unit ( + [event] + name=prestart + [modify_unit] + [filter] + id=bob + [/filter] + moves=3 + [/modify_unit] + {RETURN ( + [have_unit] + id=bob + formula="moves < max_moves" + [/have_unit] + )} + [/event] +)} + +{GENERIC_UNIT_TEST filter_fai_unit_error ( + [event] + name=prestart + {RETURN ( + [have_unit] + id=bob + formula="+ max_moves" + [/have_unit] + )} + [/event] +)} diff --git a/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj b/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj index 6881fe8161eb..042c66b9626e 100644 --- a/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj +++ b/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj @@ -158,6 +158,7 @@ 91DCA68A1C9066CC0030F8D0 /* unit_preview_pane.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91DCA6871C9066CC0030F8D0 /* unit_preview_pane.cpp */; }; 91DCA68D1C9066EC0030F8D0 /* unit_recruit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91DCA68B1C9066EC0030F8D0 /* unit_recruit.cpp */; }; 91DCA68E1C9066EC0030F8D0 /* unit_recruit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91DCA68B1C9066EC0030F8D0 /* unit_recruit.cpp */; }; + 91DCA6901C9360610030F8D0 /* test_formula_core.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91DCA68F1C9360610030F8D0 /* test_formula_core.cpp */; }; 91ECD5D21BA11A5200B25CF1 /* unit_creator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91ECD5D01BA11A5200B25CF1 /* unit_creator.cpp */; }; 91ECD5D51BA11A6400B25CF1 /* mp_replay_controller.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91ECD5D31BA11A6400B25CF1 /* mp_replay_controller.cpp */; }; 91F462841C71139C0050A9C9 /* preferences_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91F462821C71139B0050A9C9 /* preferences_dialog.cpp */; }; @@ -1493,6 +1494,7 @@ 91DCA6881C9066CC0030F8D0 /* unit_preview_pane.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_preview_pane.hpp; sourceTree = ""; }; 91DCA68B1C9066EC0030F8D0 /* unit_recruit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unit_recruit.cpp; sourceTree = ""; }; 91DCA68C1C9066EC0030F8D0 /* unit_recruit.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_recruit.hpp; sourceTree = ""; }; + 91DCA68F1C9360610030F8D0 /* test_formula_core.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test_formula_core.cpp; sourceTree = ""; }; 91ECD5D01BA11A5200B25CF1 /* unit_creator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unit_creator.cpp; sourceTree = ""; }; 91ECD5D11BA11A5200B25CF1 /* unit_creator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_creator.hpp; sourceTree = ""; }; 91ECD5D31BA11A6400B25CF1 /* mp_replay_controller.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mp_replay_controller.cpp; sourceTree = ""; }; @@ -3835,6 +3837,7 @@ B597C4A90FACD42E00CE81F5 /* main.cpp */, B597C4A80FACD42E00CE81F5 /* test_config_cache.cpp */, B597C4A70FACD42E00CE81F5 /* test_formula_ai.cpp */, + 91DCA68F1C9360610030F8D0 /* test_formula_core.cpp */, B597C4AD0FACD42E00CE81F5 /* test_lexical_cast.cpp */, B597C4AC0FACD42E00CE81F5 /* test_network_worker.cpp */, B597C4AA0FACD42E00CE81F5 /* test_serialization.cpp */, @@ -5555,6 +5558,7 @@ 91DCA6861C9066A60030F8D0 /* unit_preview_pane.cpp in Sources */, 91DCA68A1C9066CC0030F8D0 /* unit_preview_pane.cpp in Sources */, 91DCA68E1C9066EC0030F8D0 /* unit_recruit.cpp in Sources */, + 91DCA6901C9360610030F8D0 /* test_formula_core.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d9028e30242c..d59a2a01f284 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1460,6 +1460,7 @@ if(ENABLE_TESTS) tests/test_config_cache.cpp tests/test_filesystem.cpp tests/test_formula_ai.cpp + tests/test_formula_core.cpp tests/test_formula_function.cpp tests/test_image_modifications.cpp tests/test_lexical_cast.cpp diff --git a/src/SConscript b/src/SConscript index bb8bd359b851..af98b4212529 100644 --- a/src/SConscript +++ b/src/SConscript @@ -772,6 +772,7 @@ test_sources = Split(""" tests/test_config.cpp tests/test_filesystem.cpp tests/test_formula_ai.cpp + tests/test_formula_core.cpp tests/test_formula_function.cpp tests/test_image_modifications.cpp tests/test_lexical_cast.cpp diff --git a/src/ai/formula/ai.cpp b/src/ai/formula/ai.cpp index cc2e7407a90f..9b9406d55929 100644 --- a/src/ai/formula/ai.cpp +++ b/src/ai/formula/ai.cpp @@ -128,7 +128,7 @@ void formula_ai::handle_exception(game_logic::formula_error& e, const std::strin void formula_ai::display_message(const std::string& msg) const { - resources::screen->get_chat_manager().add_chat_message(time(NULL), "fai", get_side(), msg, + resources::screen->get_chat_manager().add_chat_message(time(NULL), "wfl", get_side(), msg, events::chat_handler::MESSAGE_PUBLIC, false); } diff --git a/src/ai/formula/function_table.cpp b/src/ai/formula/function_table.cpp index ae0082a51522..720e68369ff8 100644 --- a/src/ai/formula/function_table.cpp +++ b/src/ai/formula/function_table.cpp @@ -97,20 +97,6 @@ class unit_adapter { const unit* unit_; }; -class distance_between_function : public function_expression { -public: - explicit distance_between_function(const args_list& args) - : function_expression("distance_between", args, 2, 2) - {} - -private: - variant execute(const formula_callable& variables, formula_debugger *fdb) const { - const map_location loc1 = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"distance_between:location_A")))->loc(); - const map_location loc2 = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"distance_between:location_B")))->loc(); - return variant(distance_between(loc1, loc2)); - } -}; - class distance_to_nearest_unowned_village_function : public function_expression { public: @@ -1847,8 +1833,6 @@ expression_ptr ai_function_symbol_table::create_function(const std::string &fn, return expression_ptr(new close_enemies_function(args, ai_)); } else if(fn == "calculate_outcome") { return expression_ptr(new calculate_outcome_function(args, ai_)); - } else if(fn == "distance_between") { - return expression_ptr(new distance_between_function(args)); } else if(fn == "run_file") { return expression_ptr(new run_file_function(args, ai_)); } else if(fn == "calculate_map_ownership") { diff --git a/src/callable_objects.cpp b/src/callable_objects.cpp index c3b6eebd3c2b..0518d7ddf339 100644 --- a/src/callable_objects.cpp +++ b/src/callable_objects.cpp @@ -167,7 +167,7 @@ variant unit_callable::get_value(const std::string& key) const return variant(u_.name()); } else if(key == "usage") { return variant(u_.usage()); - } else if(key == "leader") { + } else if(key == "leader" || key == "canrecruit") { return variant(u_.can_recruit()); } else if(key == "undead") { return variant(u_.get_state("not_living") ? 1 : 0); @@ -198,14 +198,17 @@ variant unit_callable::get_value(const std::string& key) const return variant(u_.experience()); } else if(key == "max_experience") { return variant(u_.max_experience()); - } else if(key == "level") { + } else if(key == "level" || key == "full") { + // This allows writing "upkeep == full" return variant(u_.level()); - } else if(key == "total_movement") { + } else if(key == "total_movement" || key == "max_moves") { return variant(u_.total_movement()); - } else if(key == "movement_left") { + } else if(key == "movement_left" || key == "moves") { return variant(u_.movement_left()); } else if(key == "attacks_left") { return variant(u_.attacks_left()); + } else if(key == "max_attacks") { + return variant(u_.max_attacks()); } else if(key == "traits") { const std::vector traits = u_.get_traits_list(); std::vector res; @@ -218,7 +221,31 @@ variant unit_callable::get_value(const std::string& key) const res.push_back( variant(*it) ); } return variant( &res ); - } else if(key == "states") { + } else if(key == "extra_recruit") { + const std::vector recruits = u_.recruits(); + std::vector res; + + if(recruits.empty()) + return variant( &res ); + + for (std::vector::const_iterator it = recruits.begin(); it != recruits.end(); ++it) + { + res.push_back( variant(*it) ); + } + return variant( &res ); + } else if(key == "advances_to") { + const std::vector advances = u_.advances_to(); + std::vector res; + + if(advances.empty()) + return variant( &res ); + + for (std::vector::const_iterator it = advances.begin(); it != advances.end(); ++it) + { + res.push_back( variant(*it) ); + } + return variant( &res ); + } else if(key == "states" || key == "status") { const std::map& states_map = u_.get_states(); return convert_map( states_map ); @@ -226,12 +253,39 @@ variant unit_callable::get_value(const std::string& key) const return variant(u_.side()-1); } else if(key == "cost") { return variant(u_.cost()); + } else if(key == "upkeep") { + return variant(u_.upkeep()); + } else if(key == "loyal") { + // So we can write "upkeep == loyal" + return variant(0); + } else if(key == "hidden") { + return variant(u_.get_hidden()); + } else if(key == "petrified") { + return variant(u_.incapacitated()); + } else if(key == "resting") { + return variant(u_.resting()); + } else if(key == "role") { + return variant(u_.get_role()); + } else if(key == "race") { + return variant(u_.race()->id()); + } else if(key == "gender") { + return variant(gender_string(u_.gender())); + } else if(key == "variation") { + return variant(u_.variation()); + } else if(key == "zoc") { + return variant(u_.get_emit_zoc()); + } else if(key == "alignment") { + return variant(u_.alignment().to_string()); + } else if(key == "facing") { + return variant(map_location::write_direction(u_.facing())); } else if(key == "vars") { if(u_.formula_manager().formula_vars()) { return variant(u_.formula_manager().formula_vars().get()); } else { return variant(); } + } else if(key == "n" || key == "s" || key == "ne" || key == "se" || key == "nw" || key == "sw" || key == "lawful" || key == "neutral" || key == "chaotic" || key == "liminal" || key == "male" || key == "female") { + return variant(key); } else { return variant(); } @@ -246,7 +300,7 @@ void unit_callable::get_inputs(std::vector* inputs) c inputs->push_back(game_logic::formula_input("id", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("type", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("name", FORMULA_READ_ONLY)); - inputs->push_back(game_logic::formula_input("leader", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("canrecruit", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("undead", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("traits", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("attacks", FORMULA_READ_ONLY)); @@ -256,13 +310,27 @@ void unit_callable::get_inputs(std::vector* inputs) c inputs->push_back(game_logic::formula_input("experience", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("max_experience", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("level", FORMULA_READ_ONLY)); - inputs->push_back(game_logic::formula_input("total_movement", FORMULA_READ_ONLY)); - inputs->push_back(game_logic::formula_input("movement_left", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("moves", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("max_moves", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("attacks_left", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("max_attacks", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("side", FORMULA_READ_ONLY)); - inputs->push_back(game_logic::formula_input("states", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("extra_recruit", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("advances_to", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("status", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("cost", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("usage", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("upkeep", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("hidden", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("petrified", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("resting", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("role", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("race", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("gender", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("variation", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("zoc", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("alignment", FORMULA_READ_ONLY)); + inputs->push_back(game_logic::formula_input("facing", FORMULA_READ_ONLY)); inputs->push_back(game_logic::formula_input("vars", FORMULA_READ_ONLY)); } diff --git a/src/formula.cpp b/src/formula.cpp index 13be86485396..035183a1c278 100644 --- a/src/formula.cpp +++ b/src/formula.cpp @@ -19,6 +19,8 @@ #include "formula_callable.hpp" #include "formula_function.hpp" #include "map_utils.hpp" +#include "random_new.hpp" +#include "serialization/string_utils.hpp" #include @@ -103,7 +105,7 @@ class list_expression : public formula_expression { std::vector res; res.reserve(items_.size()); for(std::vector::const_iterator i = items_.begin(); i != items_.end(); ++i) { - res.push_back((*i)->evaluate(variables,fdb)); + res.push_back((*i)->evaluate(variables,add_debug_info(fdb, 0, "[list element]"))); } return variant(&res); @@ -142,10 +144,16 @@ class map_expression : public formula_expression { std::stringstream s; s << " ["; for(std::vector::const_iterator i = items_.begin(); ( i != items_.end() ) && ( i+1 != items_.end() ) ; i+=2) { + if(i != items_.begin()) { + s << ", "; + } s << (*i)->str(); s << " -> "; s << (*(i+1))->str(); } + if(items_.empty()) { + s << "->"; + } s << " ]"; return s.str(); } @@ -153,8 +161,8 @@ class map_expression : public formula_expression { variant execute(const formula_callable& variables, formula_debugger *fdb) const { std::map res; for(std::vector::const_iterator i = items_.begin(); ( i != items_.end() ) && ( i+1 != items_.end() ) ; i+=2) { - variant key = (*i)->evaluate(variables,fdb); - variant value = (*(i+1))->evaluate(variables,fdb); + variant key = (*i)->evaluate(variables,add_debug_info(fdb, 0, "key ->")); + variant value = (*(i+1))->evaluate(variables,add_debug_info(fdb, 1, "-> value")); res[ key ] = value; } @@ -187,7 +195,7 @@ class unary_operator_expression : public formula_expression { private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { - const variant res = operand_->evaluate(variables,fdb); + const variant res = operand_->evaluate(variables,add_debug_info(fdb, 0, op_str_ + " unary")); switch(op_) { case NOT: return res.as_bool() ? variant(0) : variant(1); @@ -202,6 +210,56 @@ class unary_operator_expression : public formula_expression { expression_ptr operand_; }; +class string_callable : public formula_callable { + variant string_; +public: + explicit string_callable(const variant& string) : string_(string) + {} + + void get_inputs(std::vector* inputs) const { + inputs->push_back(formula_input("size", FORMULA_READ_ONLY)); + inputs->push_back(formula_input("empty", FORMULA_READ_ONLY)); + inputs->push_back(formula_input("char", FORMULA_READ_ONLY)); + inputs->push_back(formula_input("word", FORMULA_READ_ONLY)); + inputs->push_back(formula_input("item", FORMULA_READ_ONLY)); + } + + variant get_value(const std::string& key) const { + if(key == "size") { + return variant(string_.as_string().length()); + } else if(key == "empty") { + return variant(string_.as_string().empty()); + } else if(key == "char" || key == "chars") { + std::vector chars; + BOOST_FOREACH(char c , string_.as_string()) { + chars.push_back(variant(std::string(1, c))); + } + return variant(&chars); + } else if(key == "word" || key == "words") { + std::vector words; + const std::string& str = string_.as_string(); + size_t next_space = 0; + do { + size_t last_space = next_space; + next_space = str.find_first_of(" \t", next_space); + words.push_back(variant(str.substr(last_space, next_space - last_space))); + next_space = str.find_first_not_of(" \t", next_space); + } while(next_space != std::string::npos); + return variant(&words); + } else if(key == "item" || key == "items") { + std::vector split = utils::parenthetical_split(string_.as_string(), ','); + std::vector items; + items.reserve(split.size()); + BOOST_FOREACH(const std::string s , split) { + items.push_back(variant(s)); + } + return variant(&items); + } else { + return variant(); + } + } +}; + class list_callable : public formula_callable { variant list_; public: @@ -239,6 +297,50 @@ class list_callable : public formula_callable { }; +class map_callable : public formula_callable { + variant map_; +public: + explicit map_callable(const variant& map) : map_(map) + {} + + void get_inputs(std::vector* inputs) const { + inputs->push_back(formula_input("size", FORMULA_READ_WRITE)); + inputs->push_back(formula_input("empty", FORMULA_READ_WRITE)); + for(variant_iterator iter = map_.begin(); iter != map_.end(); iter++) { + // variant_iterator does not implement operator->, + // and to do so is notrivial since it returns temporaries for maps. + const variant& key_variant = (*iter).get_member("key"); + if(!key_variant.is_string()) { + continue; + } + std::string key = key_variant.as_string(); + bool valid = true; + BOOST_FOREACH(char c , key) { + if(!isalpha(c) && c != '_') { + valid = false; + break; + } + } + if(valid) { + inputs->push_back(formula_input(key, FORMULA_READ_ONLY)); + } + } + } + + variant get_value(const std::string& key) const { + const variant key_variant(key); + if(map_.as_map().find(key_variant) != map_.as_map().end()) { + return map_[key_variant]; + } else if(key == "size") { + return variant(map_.num_elements()); + } else if(key == "empty") { + return variant(map_.num_elements() == 0); + } else { + return variant(); + } + } +}; + class dot_callable : public formula_callable { public: dot_callable(const formula_callable &global, @@ -274,19 +376,29 @@ class dot_expression : public formula_expression { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { - const variant left = left_->evaluate(variables,add_debug_info(fdb,0,"left.")); + const variant left = left_->evaluate(variables,add_debug_info(fdb,0,"left .")); if(!left.is_callable()) { if(left.is_list()) { list_callable list_call(left); dot_callable callable(variables, list_call); return right_->evaluate(callable,fdb); } + if(left.is_map()) { + map_callable map_call(left); + dot_callable callable(variables, map_call); + return right_->evaluate(callable,fdb); + } + if(left.is_string()) { + string_callable string_call(left); + dot_callable callable(variables, string_call); + return right_->evaluate(callable,fdb); + } return left; } dot_callable callable(variables, *left.as_callable()); - return right_->evaluate(callable,add_debug_info(fdb,1,".right")); + return right_->evaluate(callable,add_debug_info(fdb,1,". right")); } expression_ptr left_, right_; @@ -306,8 +418,8 @@ class square_bracket_expression : public formula_expression { } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { - const variant left = left_->evaluate(variables,fdb); - const variant key = key_->evaluate(variables,fdb); + const variant left = left_->evaluate(variables,add_debug_info(fdb,0,"base[]")); + const variant key = key_->evaluate(variables,add_debug_info(fdb,1,"[index]")); if(left.is_list() || left.is_map()) { return left[ key ]; } else { @@ -342,19 +454,23 @@ class operator_expression : public formula_expression { op_ = MULL; } else if(op == "./") { op_ = DIVL; + } else if(op == "..") { + op_ = CAT; + } else if(op == "in") { + op_ = IN; } } std::string str() const { std::stringstream s; - s << left_->str() << op_str_ << right_->str(); + s << '(' << left_->str() << op_str_ << right_->str() << ')'; return s.str(); } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { - const variant left = left_->evaluate(variables,add_debug_info(fdb,0,"left_OP")); - const variant right = right_->evaluate(variables,add_debug_info(fdb,1,"OP_right")); + const variant left = left_->evaluate(variables,add_debug_info(fdb,0,"left " + op_str_)); + const variant right = right_->evaluate(variables,add_debug_info(fdb,1,op_str_ + " right")); switch(op_) { case AND: return left.as_bool() == false ? left : right; @@ -378,6 +494,10 @@ class operator_expression : public formula_expression { return left.list_elements_mul(right); case DIVL: return left.list_elements_div(right); + case IN: + return variant(right.contains(left)); + case CAT: + return left.concatenate(right); case EQ: return left == right ? variant(1) : variant(0); case NEQ: @@ -392,22 +512,29 @@ class operator_expression : public formula_expression { return left > right ? variant(1) : variant(0); case MOD: return left % right; + case RAN: + return left.build_range(right); case DICE: - default: return variant(dice_roll(left.as_int(), right.as_int())); + default: + std::cerr << "ERROR: Unimplemented operator!" << std::endl; + return variant(); } } static int dice_roll(int num_rolls, int faces) { int res = 0; while(faces > 0 && num_rolls-- > 0) { - // TODO: should we use the synced rng here ? - res += (rand()%faces)+1; + if(random_new::generator) { + res += (random_new::generator->next_random() % faces) + 1; + } else { + res += (rand() % faces) + 1; + } } return res; } - enum OP { AND, OR, NEQ, LTE, GTE, GT='>', LT='<', EQ='=', + enum OP { AND, OR, NEQ, LTE, GTE, CAT, IN, GT='>', LT='<', EQ='=', RAN='~', ADD='+', SUB='-', MUL='*', DIV='/', ADDL, SUBL, MULL, DIVL, DICE='d', POW='^', MOD='%' }; OP op_; @@ -422,11 +549,12 @@ typedef std::map exp_table_evaluated; class where_variables: public formula_callable { public: where_variables(const formula_callable &base, - expr_table_ptr table ) + expr_table_ptr table, formula_debugger* fdb ) : formula_callable(false) , base_(base) , table_(table) , evaluated_table_() + , debugger_(fdb) { } @@ -434,6 +562,7 @@ class where_variables: public formula_callable { const formula_callable& base_; expr_table_ptr table_; mutable exp_table_evaluated evaluated_table_; + formula_debugger* debugger_; void get_inputs(std::vector* inputs) const { for(expr_table::const_iterator i = table_->begin(); i != table_->end(); ++i) { @@ -448,7 +577,7 @@ class where_variables: public formula_callable { if( ev != evaluated_table_.end()) return ev->second; - variant v = i->second->evaluate(base_); + variant v = i->second->evaluate(base_, add_debug_info(debugger_, 0, "where[" + key + "]")); evaluated_table_[key] = v; return v; } @@ -480,8 +609,8 @@ class where_expression: public formula_expression { expr_table_ptr clauses_; variant execute(const formula_callable& variables,formula_debugger *fdb) const { - where_variables wrapped_variables(variables, clauses_); - return body_->evaluate(wrapped_variables,fdb); + where_variables wrapped_variables(variables, clauses_, fdb); + return body_->evaluate(wrapped_variables,add_debug_info(fdb, 0, "... where")); } }; @@ -557,20 +686,44 @@ class string_expression : public formula_expression { str_(), subs_() { - std::string::iterator i; - while((i = std::find(str.begin(), str.end(), '{')) != str.end()) { - std::string::iterator j = std::find(i, str.end(), '}'); + std::string::iterator i = str.begin(); + while((i = std::find(i, str.end(), '[')) != str.end()) { + int bracket_depth = 0; + std::string::iterator j = i + 1; + while(j != str.end() && (bracket_depth > 0 || *j != ']')) { + if(*j == '[') { + bracket_depth++; + } else if(*j == ']' && bracket_depth > 0) { + bracket_depth--; + } + ++j; + } if(j == str.end()) { break; } const std::string formula_str(i+1, j); const int pos = i - str.begin(); - str.erase(i, j+1); + if(j - i == 2 && (i[1] == '(' || i[1] == '\'' || i[1] == ')')) { + // Bracket contained nothing but a quote or parenthesis. + // This means it was intended as a literal quote or square bracket. + i = str.erase(i); + if(*i == '(') *i = '['; + else if(*i == ')') *i = ']'; + i = str.erase(i + 1); + continue; + } else { + i = str.erase(i, j+1); + } substitution sub; sub.pos = pos; - sub.calculation.reset(new formula(formula_str)); + try { + sub.calculation.reset(new formula(formula_str)); + } catch(formula_error& e) { + e.filename += " - string substitution"; + throw e; + } subs_.push_back(sub); } @@ -581,7 +734,33 @@ class string_expression : public formula_expression { std::string str() const { - return str_.as_string(); + std::string res = str_.as_string(); + int j = res.size() - 1; + for(size_t i = 0; i < subs_.size(); ++i) { + const substitution& sub = subs_[i]; + for(;j >= sub.pos && j >= 0;j--) { + if(res[j] == '\'') { + res.replace(j, 1, "[']"); + } else if(res[j] == '[') { + res.replace(j, 1, "[(]"); + } else if(res[j] == ']') { + res.replace(j, 1, "[)]"); + } + } + const std::string str = "[" + sub.calculation->str() + "]"; + res.insert(sub.pos, str); + } + for(;j >= 0;j--) { + if(res[j] == '\'') { + res.replace(j, 1, "[']"); + } else if(res[j] == '[') { + res.replace(j, 1, "[(]"); + } else if(res[j] == ']') { + res.replace(j, 1, "[)]"); + } + } + + return "'" + res + "'"; } private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { @@ -590,7 +769,9 @@ class string_expression : public formula_expression { } else { std::string res = str_.as_string(); for(size_t i=0; i < subs_.size(); ++i) { + const int j = subs_.size() - i - 1; const substitution& sub = subs_[i]; + add_debug_info(fdb, j, "[string subst]"); const std::string str = sub.calculation->evaluate(variables,fdb).string_cast(); res.insert(sub.pos, str); } @@ -631,8 +812,10 @@ int operator_precedence(const token& t) precedence_map[">"] = n; precedence_map["<="] = n; precedence_map[">="] = n; + precedence_map["~"] = ++n; precedence_map["+"] = ++n; precedence_map["-"] = n; + precedence_map[".."] = n; precedence_map["*"] = ++n; precedence_map["/"] = n; precedence_map["%"] = ++n; @@ -838,6 +1021,34 @@ expression_ptr parse_expression(const token* i1, const token* i2, function_symbo } } } + + if((i2-1)->type == TOKEN_RSQUARE) { // Check if there is [ ] : a indexing operator + const token* tok = i2-2; + int square_parens = 0; + bool is_index_op = true; + while((tok->type != TOKEN_LSQUARE || square_parens) && tok != i1) { + if (tok->type == TOKEN_RSQUARE) { + square_parens++; + } else if(tok->type == TOKEN_LSQUARE) { + square_parens--; + } else if((tok->type == TOKEN_POINTER || tok->type == TOKEN_COMMA) && !square_parens ) { + is_index_op = false; + } + --tok; + } + if(tok == i1 || tok->type != TOKEN_LSQUARE) { + is_index_op = false; + } + if(is_index_op) { + try { + return expression_ptr(new square_bracket_expression( + parse_expression(i1,tok,symbols), + parse_expression(tok+1,i2-1,symbols))); + } catch (formula_error& e){ + throw formula_error(e.type, tokens_to_string(i1, i2-1), *i1->filename, i1->line_number); + } + } + } int parens = 0; const token* op = NULL; @@ -850,7 +1061,9 @@ expression_ptr parse_expression(const token* i1, const token* i2, function_symbo } else if(parens == 0 && i->type == TOKEN_OPERATOR) { if( ( !operator_group ) && (op == NULL || operator_precedence(*op) >= operator_precedence(*i)) ) { - op = i; + // Need special exception for exponentiation to be right-associative + if(*i->begin != '^' || op == NULL || *op->begin != '^') + op = i; } operator_group = true; } else { @@ -861,44 +1074,36 @@ expression_ptr parse_expression(const token* i1, const token* i2, function_symbo if(op == NULL) { if(i1->type == TOKEN_LPARENS && (i2-1)->type == TOKEN_RPARENS) { return parse_expression(i1+1,i2-1,symbols); - } else if( (i2-1)->type == TOKEN_RSQUARE) { //check if there is [ ] : either a list/map definition, or a operator - const token* tok = i2-2; - int square_parens = 0; - bool is_map = false; - while ( (tok->type != TOKEN_LSQUARE || square_parens) && tok != i1) { - if (tok->type == TOKEN_RSQUARE) { - square_parens++; - } else if(tok->type == TOKEN_LSQUARE) { - square_parens--; - } else if( (tok->type == TOKEN_POINTER) && !square_parens ) { - is_map = true; - } - --tok; - } - if (tok->type == TOKEN_LSQUARE) { - if (tok == i1) { - //create a list or a map - std::vector args; - - if ( is_map ) { - parse_set_args(i1+1, i2-1, &args, symbols); - return expression_ptr(new map_expression(args)); - } else { - parse_args(i1+1,i2-1,&args,symbols); - return expression_ptr(new list_expression(args)); - } - } else { - //execute operator [ ] - try{ - return expression_ptr(new square_bracket_expression( - parse_expression(i1,tok,symbols), - parse_expression(tok+1,i2-1,symbols))); - } - catch (formula_error& e){ - throw formula_error( e.type, tokens_to_string(i1, i2-1), *i1->filename, i1->line_number ); - } + } else if((i2-1)->type == TOKEN_RSQUARE) { // Check if there is [ ] : a list/map definition + // First, a special case for an empty map + if(i2 - i1 == 3 && i1->type == TOKEN_LSQUARE && (i1+1)->type == TOKEN_POINTER) { + return expression_ptr(new map_expression(std::vector())); + } + const token* tok = i2-2; + int square_parens = 0; + bool is_map = false; + while ((tok->type != TOKEN_LSQUARE || square_parens) && tok != i1) { + if (tok->type == TOKEN_RSQUARE) { + square_parens++; + } else if(tok->type == TOKEN_LSQUARE) { + square_parens--; + } else if( (tok->type == TOKEN_POINTER) && !square_parens ) { + is_map = true; } + --tok; + } + if (tok->type == TOKEN_LSQUARE && tok == i1) { + //create a list or a map + std::vector args; + + if (is_map) { + parse_set_args(i1+1, i2-1, &args, symbols); + return expression_ptr(new map_expression(args)); + } else { + parse_args(i1+1,i2-1,&args,symbols); + return expression_ptr(new list_expression(args)); } + } } else if(i2 - i1 == 1) { if(i1->type == TOKEN_KEYWORD) { if(std::string(i1->begin,i1->end) == "functions") { @@ -1000,6 +1205,13 @@ expression_ptr parse_expression(const token* i1, const token* i2, function_symbo } +formula::~formula() +{ + if(managing_symbols) { + delete symbols_; + } +} + formula_ptr formula::create_optional_formula(const std::string& str, function_symbol_table* symbols) { if(str.empty()) { @@ -1011,15 +1223,23 @@ formula_ptr formula::create_optional_formula(const std::string& str, function_sy formula::formula(const std::string& str, function_symbol_table* symbols) : expr_(), - str_(str) + str_(str), + symbols_(symbols), + managing_symbols(symbols == NULL) { using namespace formula_tokenizer; + + if(symbols == NULL) { + symbols_ = new function_symbol_table; + } std::vector tokens; std::string::const_iterator i1 = str.begin(), i2 = str.end(); //set true when 'fai' keyword is found bool fai_keyword = false; + //set true when 'wfl' keyword is found + bool wfl_keyword = false; //used to locally keep the track of which file we parse actually and in which line we are std::vector< std::pair< std::string, int> > files; //used as a source of strings - we point to these strings from tokens @@ -1057,6 +1277,10 @@ formula::formula(const std::string& str, function_symbol_table* symbols) : fai_keyword = true; tokens.pop_back(); } else + if( ( current_type == TOKEN_KEYWORD) && ( std::string(tokens.back().begin,tokens.back().end) == "wfl") ) { + wfl_keyword = true; + tokens.pop_back(); + } else if( ( current_type == TOKEN_KEYWORD) && ( std::string(tokens.back().begin,tokens.back().end) == "faiend") ) { if (files.size() > 1) { files.pop_back(); @@ -1066,7 +1290,16 @@ formula::formula(const std::string& str, function_symbol_table* symbols) : throw formula_error("Unexpected 'faiend' found", "", "", 0); } } else - if (fai_keyword) { + if( ( current_type == TOKEN_KEYWORD) && ( std::string(tokens.back().begin,tokens.back().end) == "wflend") ) { + if (files.size() > 1) { + files.pop_back(); + filenames_it = filenames.find( files.back().first ); + tokens.pop_back(); + } else { + throw formula_error("Unexpected 'wflend' found", "", "", 0); + } + } else + if (fai_keyword || wfl_keyword) { if(current_type == TOKEN_STRING_LITERAL) { std::string str = std::string(tokens.back().begin,tokens.back().end); files.push_back( std::make_pair( str , 1 ) ); @@ -1075,12 +1308,17 @@ formula::formula(const std::string& str, function_symbol_table* symbols) : if(ret.second==true) { filenames_it = ret.first; } else { - throw formula_error("Faifile already included", "fai" + str, "", 0); + if (fai_keyword) + throw formula_error("Faifile already included", "fai" + str, "", 0); + else throw formula_error("Wflfile already included", "wfl" + str, "", 0); } tokens.pop_back(); fai_keyword = false; + wfl_keyword = false; } else { - throw formula_error("Expected string after the 'fai'", "fai", "", 0); + if (fai_keyword) + throw formula_error("Expected string after the 'fai'", "fai", "", 0); + else throw formula_error("Expected string after the 'wfl'", "wfl", "", 0); } } else { //in every token not specified above, store line number and name of file it came from @@ -1106,19 +1344,24 @@ formula::formula(const std::string& str, function_symbol_table* symbols) : } if(files.size() > 1) { - throw formula_error("Missing 'faiend', make sure each .fai file ends with it", "", "", 0); + throw formula_error("Missing 'wflend', make sure each .wfl file ends with it", "", "", 0); } if(!tokens.empty()) { - expr_ = parse_expression(&tokens[0],&tokens[0] + tokens.size(), symbols); + expr_ = parse_expression(&tokens[0],&tokens[0] + tokens.size(), symbols_); } else { expr_ = expression_ptr(new null_expression()); } } formula::formula(const token* i1, const token* i2, function_symbol_table* symbols) : expr_(), - str_() + str_(), + symbols_(symbols), + managing_symbols(symbols == NULL) { + if(symbols == NULL) { + symbols_ = new function_symbol_table; + } if(i1 != i2) { expr_ = parse_expression(i1,i2, symbols); @@ -1160,96 +1403,3 @@ formula_error::formula_error(const std::string& type, const std::string& formula } -#ifdef UNIT_TEST_FORMULA -using namespace game_logic; -class mock_char : public formula_callable { - variant get_value(const std::string& key) const { - if(key == "strength") { - return variant(15); - } else if(key == "agility") { - return variant(12); - } - - return variant(10); - } -}; -class mock_party : public formula_callable { - variant get_value(const std::string& key) const { - if(key == "members") { - i_[0].add("strength",variant(12)); - i_[1].add("strength",variant(16)); - i_[2].add("strength",variant(14)); - std::vector members; - for(int n = 0; n != 3; ++n) { - members.push_back(variant(&i_[n])); - } - - return variant(&members); - } else if(key == "char") { - return variant(&c_); - } else { - return variant(0); - } - } - - mock_char c_; - mutable map_formula_callable i_[3]; - -}; - -#include - -int main() -{ - srand(time(NULL)); - try { - mock_char c; - mock_party p; - - assert(formula("strength").execute(c).as_int() == 15); - assert(formula("17").execute(c).as_int() == 17); - assert(formula("strength/2 + agility").execute(c).as_int() == 19); - assert(formula("(strength+agility)/2").execute(c).as_int() == 13); - assert(formula("strength > 12").execute(c).as_int() == 1); - assert(formula("strength > 18").execute(c).as_int() == 0); - assert(formula("if(strength > 12, 7, 2)").execute(c).as_int() == 7); - assert(formula("if(strength > 18, 7, 2)").execute(c).as_int() == 2); - assert(formula("2 and 1").execute(c).as_int() == 1); - assert(formula("2 and 0").execute(c).as_int() == 0); - assert(formula("2 or 0").execute(c).as_int() == 1); - assert(formula("-5").execute(c).as_int() == -5); - assert(formula("not 5").execute(c).as_int() == 0); - assert(formula("not 0").execute(c).as_int() == 1); - assert(formula("abs(5)").execute(c).as_int() == 5); - assert(formula("abs(-5)").execute(c).as_int() == 5); - assert(formula("min(3,5)").execute(c).as_int() == 3); - assert(formula("min(5,2)").execute(c).as_int() == 2); - assert(formula("max(3,5)").execute(c).as_int() == 5); - assert(formula("max(5,2)").execute(c).as_int() == 5); - assert(formula("max(4,5,[2,18,7])").execute(c).as_int() == 18); - assert(formula("char.strength").execute(p).as_int() == 15); - assert(formula("choose(members,strength).strength").execute(p).as_int() == 16); - assert(formula("4^2").execute().as_int() == 16); - assert(formula("2+3^3").execute().as_int() == 29); - assert(formula("2*3^3+2").execute().as_int() == 56); - assert(formula("9^3").execute().as_int() == 729); - assert(formula("x*5 where x=1").execute().as_int() == 5); - assert(formula("x*(a*b where a=2,b=1) where x=5").execute().as_int() == 10); - assert(formula("char.strength * ability where ability=3").execute(p).as_int() == 45); - assert(formula("'abcd' = 'abcd'").execute(p).as_bool() == true); - assert(formula("'abcd' = 'acd'").execute(p).as_bool() == false); - assert(formula("'strength, agility: {strength}, {agility}'").execute(c).as_string() == - "strength, agility: 15, 12"); - const int dice_roll = formula("3d6").execute().as_int(); - assert(dice_roll >= 3 && dice_roll <= 18); - - variant myarray = formula("[1,2,3]").execute(); - assert(myarray.num_elements() == 3); - assert(myarray[0].as_int() == 1); - assert(myarray[1].as_int() == 2); - assert(myarray[2].as_int() == 3); - } catch(formula_error& e) { - std::cerr << "parse error\n"; - } -} -#endif diff --git a/src/formula.hpp b/src/formula.hpp index 6d351d049122..6a9874252ed7 100644 --- a/src/formula.hpp +++ b/src/formula.hpp @@ -59,6 +59,7 @@ class formula { static formula_ptr create_optional_formula(const std::string& str, function_symbol_table* symbols=NULL); explicit formula(const std::string& str, function_symbol_table* symbols=NULL); explicit formula(const formula_tokenizer::token* i1, const formula_tokenizer::token* i2, function_symbol_table* symbols=NULL); + ~formula(); const std::string& str() const { return str_; } private: @@ -68,6 +69,8 @@ class formula { {} expression_ptr expr_; std::string str_; + function_symbol_table* symbols_; + bool managing_symbols; friend class formula_debugger; }; diff --git a/src/formula_debugger.cpp b/src/formula_debugger.cpp index 33891084cc0d..de442427258a 100644 --- a/src/formula_debugger.cpp +++ b/src/formula_debugger.cpp @@ -111,11 +111,11 @@ formula_debugger::~formula_debugger() static void msg(const char *act, debug_info &i, const char *to="", const char *result = "") { - DBG_FDB << "#" << i.counter() << act << std::endl <<" \""<< i.name().c_str() << "\"='" << i.str().c_str() << "' " << to << result << std::endl; + DBG_FDB << "#" << i.counter() << act << std::endl <<" \""<< i.name() << "\"='" << i.str() << "' " << to << result << std::endl; } -void formula_debugger::add_debug_info(int arg_number, const char *f_name) +void formula_debugger::add_debug_info(int arg_number, const std::string& f_name) { arg_number_extra_debug_info = arg_number; f_name_extra_debug_info = f_name; diff --git a/src/formula_debugger.hpp b/src/formula_debugger.hpp index b7c040fb8505..6467c7626916 100644 --- a/src/formula_debugger.hpp +++ b/src/formula_debugger.hpp @@ -81,7 +81,7 @@ class formula_debugger { virtual ~formula_debugger(); - void add_debug_info(int arg_number, const char *f_name); + void add_debug_info(int arg_number, const std::string& f_name); void call_stack_push(const std::string &str); @@ -134,7 +134,7 @@ class formula_debugger { //static functions - static formula_debugger* add_debug_info(formula_debugger *fdb, int arg_number, const char *f_name) + static formula_debugger* add_debug_info(formula_debugger *fdb, int arg_number, const std::string& f_name) { if (fdb==NULL) { return NULL; @@ -150,7 +150,7 @@ class formula_debugger { std::deque< breakpoint_ptr > breakpoints_; std::deque execution_trace_; int arg_number_extra_debug_info; - const char *f_name_extra_debug_info; + std::string f_name_extra_debug_info; }; diff --git a/src/formula_debugger_fwd.cpp b/src/formula_debugger_fwd.cpp index fbb1832e04d0..a35dfc4184f8 100644 --- a/src/formula_debugger_fwd.cpp +++ b/src/formula_debugger_fwd.cpp @@ -23,7 +23,7 @@ namespace game_logic { -formula_debugger* add_debug_info(formula_debugger *fdb, int arg_number, const char *f_name) +formula_debugger* add_debug_info(formula_debugger *fdb, int arg_number, const std::string& f_name) { if (fdb==NULL) { return NULL; diff --git a/src/formula_debugger_fwd.hpp b/src/formula_debugger_fwd.hpp index 86667ddc045c..3949f8d1760f 100644 --- a/src/formula_debugger_fwd.hpp +++ b/src/formula_debugger_fwd.hpp @@ -41,7 +41,7 @@ class base_breakpoint; typedef boost::shared_ptr breakpoint_ptr; -formula_debugger* add_debug_info(formula_debugger *fdb, int arg_number, const char *f_name); +formula_debugger* add_debug_info(formula_debugger *fdb, int arg_number, const std::string& f_name); variant evaluate_arg_callback(formula_debugger &fdb, const formula_expression &expression, const formula_callable &variables); diff --git a/src/formula_function.cpp b/src/formula_function.cpp index f82e50853e7f..2e3314b573f6 100644 --- a/src/formula_function.cpp +++ b/src/formula_function.cpp @@ -19,6 +19,7 @@ #include "formula_debugger.hpp" #include "formula_function.hpp" #include "game_display.hpp" +#include "game_config.hpp" #include "log.hpp" #include @@ -113,7 +114,7 @@ class dir_function : public function_expression { class if_function : public function_expression { public: explicit if_function(const args_list& args) - : function_expression("if", args, 3, -1) + : function_expression("if", args, 2, -1) {} private: @@ -165,8 +166,14 @@ class abs_function : public function_expression { private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { - const int n = args()[0]->evaluate(variables,fdb).as_int(); - return variant(n >= 0 ? n : -n); + const variant input = args()[0]->evaluate(variables,fdb); + if(input.is_decimal()) { + const int n = input.as_decimal(); + return variant(n >= 0 ? n : -n, variant::DECIMAL_VARIANT); + } else { + const int n = input.as_int(); + return variant(n >= 0 ? n : -n); + } } }; @@ -179,25 +186,25 @@ class min_function : public function_expression { private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { bool found = false; - int res = 0; + variant res(0); for(size_t n = 0; n != args().size(); ++n) { const variant v = args()[n]->evaluate(variables,fdb); if(v.is_list()) { for(size_t m = 0; m != v.num_elements(); ++m) { - if(!found || v[m].as_int() < res) { - res = v[m].as_int(); + if(!found || v[m] < res) { + res = v[m]; found = true; } } - } else if(v.is_int()) { - if(!found || v.as_int() < res) { - res = v.as_int(); + } else if(v.is_int() || v.is_decimal()) { + if(!found || v < res) { + res = v; found = true; } } } - return variant(res); + return res; } }; @@ -210,25 +217,25 @@ class max_function : public function_expression { private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { bool found = false; - int res = 0; + variant res(0); for(size_t n = 0; n != args().size(); ++n) { const variant v = args()[n]->evaluate(variables,fdb); if(v.is_list()) { for(size_t m = 0; m != v.num_elements(); ++m) { - if(!found || v[m].as_int() > res) { - res = v[m].as_int(); + if(!found || v[m] > res) { + res = v[m]; found = true; } } - } else if(v.is_int()) { - if(!found || v.as_int() > res) { - res = v.as_int(); + } else if(v.is_int() || v.is_decimal()) { + if(!found || v > res) { + res = v; found = true; } } } - return variant(res); + return res; } }; @@ -282,12 +289,18 @@ class debug_print_function : public function_expression { { str1 = var1.to_debug_string(NULL, true); LOG_SF << str1 << std::endl; + if(game_config::debug) { + game_display::get_singleton()->get_chat_manager().add_chat_message(time(NULL), "WFL", 0, str1, events::chat_handler::MESSAGE_PUBLIC, false); + } return var1; } else { str1 = var1.string_cast(); const variant var2 = args()[1]->evaluate(variables,fdb); str2 = var2.to_debug_string(NULL, true); LOG_SF << str1 << str2 << std::endl; + if(game_config::debug) { + game_display::get_singleton()->get_chat_manager().add_chat_message(time(NULL), str1, 0, str2, events::chat_handler::MESSAGE_PUBLIC, false); + } return var2; } } @@ -373,15 +386,13 @@ class tomap_function : public function_expression { } }; -class substring_function - : public function_expression { +class substring_function : public function_expression { public: explicit substring_function(const args_list& args) : function_expression("substring", args, 2, 3) {} - - variant execute(const formula_callable& variables - , formula_debugger *fdb) const { +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { std::string result = args()[0]->evaluate(variables, fdb).as_string(); @@ -389,31 +400,19 @@ class substring_function if(offset < 0) { offset += result.size(); if(offset < 0) { - WRN_SF << "[substring] Offset '" - << args()[1]->evaluate(variables, fdb).as_int() - << "' results in a negative start in string '" - << result - << "' and is reset at the beginning of the string.\n"; - offset = 0; } } else { if(static_cast(offset) >= result.size()) { - WRN_SF << "[substring] Offset '" << offset - << "' is larger than the size of '" << result - << "' and results in an empty string.\n"; - return variant(std::string()); } } if(args().size() > 2) { - const int size = args()[2]->evaluate(variables, fdb).as_int(); + int size = args()[2]->evaluate(variables, fdb).as_int(); if(size < 0) { - ERR_SF << "[substring] Size is negative an " - << "empty string is returned.\n"; - - return variant(std::string()); + size = -size; + offset = std::max(0, offset - size + 1); } return variant(result.substr(offset, size)); } else { @@ -422,79 +421,268 @@ class substring_function } }; -class length_function - : public function_expression { +class replace_function : public function_expression { public: - explicit length_function(const args_list& args) - : function_expression("length", args, 1, 1) + explicit replace_function(const args_list& args) + : function_expression("replace", args, 3, 4) {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + + std::string result = args()[0]->evaluate(variables, fdb).as_string(); + std::string replacement = args().back()->evaluate(variables, fdb).as_string(); + + int offset = args()[1]->evaluate(variables, fdb).as_int(); + if(offset < 0) { + offset += result.size(); + if(offset < 0) { + offset = 0; + } + } else { + if(static_cast(offset) >= result.size()) { + return variant(result); + } + } - variant execute(const formula_callable& variables - , formula_debugger *fdb) const { + if(args().size() > 2) { + int size = args()[2]->evaluate(variables, fdb).as_int(); + if(size < 0) { + size = -size; + offset = std::max(0, offset - size + 1); + } + return variant(result.replace(offset, size, replacement)); + } else { + return variant(result.replace(offset, std::string::npos, replacement)); + } + } +}; - return variant( - args()[0]->evaluate(variables, fdb).as_string().length()); +class length_function : public function_expression { +public: + explicit length_function(const args_list& args) + : function_expression("length", args, 1, 1) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + return variant(args()[0]->evaluate(variables, fdb).as_string().length()); } }; -class concatenate_function - : public function_expression { +class concatenate_function : public function_expression { public: explicit concatenate_function(const args_list& args) : function_expression("concatenate", args, 1, -1) {} - private: - variant execute(const formula_callable& variables - , formula_debugger *fdb) const { - - std::string result; + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + std::string result; - BOOST_FOREACH(expression_ptr arg, args()) { - result += arg->evaluate(variables, fdb).string_cast(); - } + BOOST_FOREACH(expression_ptr arg, args()) { + result += arg->evaluate(variables, fdb).string_cast(); + } - return variant(result); + return variant(result); } }; -class sin_function - : public function_expression { +class sin_function : public function_expression { public: explicit sin_function(const args_list& args) : function_expression("sin", args, 1, 1) {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double angle = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = sin(angle * pi() / 180.0); + return variant(result, variant::DECIMAL_VARIANT); + } +}; + +class cos_function : public function_expression { +public: + explicit cos_function(const args_list& args) + : function_expression("cos", args, 1, 1) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double angle = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = cos(angle * pi() / 180.0); + return variant(result, variant::DECIMAL_VARIANT); + } +}; +class tan_function : public function_expression { +public: + explicit tan_function(const args_list& args) + : function_expression("tan", args, 1, 1) + {} private: - variant execute(const formula_callable& variables - , formula_debugger *fdb) const { + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double angle = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = tan(angle * pi() / 180.0); + if(result != result || result <= INT_MIN || result >= INT_MAX) { + return variant(); + } + return variant(result, variant::DECIMAL_VARIANT); + } +}; - const double angle = - args()[0]->evaluate(variables,fdb).as_decimal() / 1000.; +class asin_function : public function_expression { +public: + explicit asin_function(const args_list& args) + : function_expression("asin", args, 1, 1) + {} - return variant( - static_cast(1000. * sin(angle * pi() / 180.)) - , variant::DECIMAL_VARIANT); +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double num = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = asin(num) * 180.0 / pi(); + if(result != result) { + return variant(); + } + return variant(result, variant::DECIMAL_VARIANT); } }; -class cos_function - : public function_expression { +class acos_function : public function_expression { public: - explicit cos_function(const args_list& args) - : function_expression("cos", args, 1, 1) + explicit acos_function(const args_list& args) + : function_expression("acos", args, 1, 1) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double num = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = acos(num) * 180.0 / pi(); + if(result != result) { + return variant(); + } + return variant(result, variant::DECIMAL_VARIANT); + } +}; + +class atan_function : public function_expression { +public: + explicit atan_function(const args_list& args) + : function_expression("acos", args, 1, 1) {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double num = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = atan(num) * 180.0 / pi(); + return variant(result, variant::DECIMAL_VARIANT); + } +}; +class sqrt_function : public function_expression { +public: + explicit sqrt_function(const args_list& args) + : function_expression("sqrt", args, 1, 1) + {} private: - variant execute(const formula_callable& variables - , formula_debugger *fdb) const { + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double num = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = sqrt(num); + if(result != result) { + return variant(); + } + return variant(result, variant::DECIMAL_VARIANT); + } +}; - const double angle = - args()[0]->evaluate(variables,fdb).as_decimal() / 1000.; +class cbrt_function : public function_expression { +public: + explicit cbrt_function(const args_list& args) + : function_expression("cbrt", args, 1, 1) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double num = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = num < 0 ? -pow(-num, 1.0l / 3.0l) : pow(num, 1.0l / 3.0l); + return variant(result, variant::DECIMAL_VARIANT); + } +}; - return variant( - static_cast(1000. * cos(angle * pi() / 180.)) - , variant::DECIMAL_VARIANT); +class root_function : public function_expression { +public: + explicit root_function(const args_list& args) + : function_expression("root", args, 2, 2) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double base = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double root = args()[1]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = base < 0 && fmod(root,2) == 1 ? -pow(-base, 1.0l / root) : pow(base, 1.0l / root); + if(result != result) { + return variant(); + } + return variant(result, variant::DECIMAL_VARIANT); + } +}; + +class log_function : public function_expression { +public: + explicit log_function(const args_list& args) + : function_expression("log", args, 1, 2) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double num = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + if(args().size() == 1) { + const double result = log(num); + if(result != result) { + return variant(); + } + return variant(result, variant::DECIMAL_VARIANT); + } else { + const double base = args()[1]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = log(num) / log(base); + if(result != result) { + return variant(); + } + return variant(result, variant::DECIMAL_VARIANT); + } + } +}; + +class exp_function : public function_expression { +public: + explicit exp_function(const args_list& args) + : function_expression("exp", args, 1, 1) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double num = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double result = exp(num); + if(result == 0 || result >= INT_MAX) { + // These are range errors rather than NaNs, + // but I figure it's better than returning INT_MIN. + return variant(); + } + return variant(result, variant::DECIMAL_VARIANT); + } +}; + +class pi_function : public function_expression { +public: + explicit pi_function(const args_list& args) + : function_expression("pi", args, 0, 0) + {} +private: + variant execute(const formula_callable&, formula_debugger*) const { + return variant(pi(), variant::DECIMAL_VARIANT); + } +}; + +class hypot_function : public function_expression { +public: + explicit hypot_function(const args_list& args) + : function_expression("hypot", args, 2, 2) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const double x = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0; + const double y = args()[1]->evaluate(variables,fdb).as_decimal() / 1000.0; + return variant(hypot(x,y), variant::DECIMAL_VARIANT); } }; @@ -635,6 +823,28 @@ class sort_function : public function_expression { } }; +class reverse_function : public function_expression { +public: + explicit reverse_function(const args_list& args) + : function_expression("reverse", args, 1, 1) + {} + +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const variant& arg = args()[0]->evaluate(variables,fdb); + if(arg.is_string()) { + std::string str = args()[0]->evaluate(variables,fdb).as_string(); + std::reverse(str.begin(), str.end()); + return variant(str); + } else if(arg.is_list()) { + std::vector list = args()[0]->evaluate(variables,fdb).as_list(); + std::reverse(list.begin(), list.end()); + return variant(&list); + } + return variant(); + } +}; + class contains_string_function : public function_expression { public: explicit contains_string_function(const args_list& args) @@ -646,29 +856,23 @@ class contains_string_function : public function_expression { std::string str = args()[0]->evaluate(variables,fdb).as_string(); std::string key = args()[1]->evaluate(variables,fdb).as_string(); - if (key.size() > str.size()) - return variant(0); - - std::string::iterator str_it, key_it, tmp_it; + return variant(str.find(key) != std::string::npos); + } +}; - for(str_it = str.begin(); str_it != str.end() - (key.size()-1); ++str_it) - { - key_it = key.begin(); - if((key_it) == key.end()) { - return variant(1); - } - tmp_it = str_it; +class find_string_function : public function_expression { +public: + explicit find_string_function(const args_list& args) + : function_expression("find_string", args, 2, 2) + {} - while( *tmp_it == *key_it) - { - if( ++key_it == key.end()) - return variant(1); - if( ++tmp_it == str.end()) - return variant(0); - } - } +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const std::string str = args()[0]->evaluate(variables,fdb).as_string(); + const std::string key = args()[1]->evaluate(variables,fdb).as_string(); - return variant(0); + size_t pos = str.find(key); + return variant(static_cast(pos)); } }; @@ -787,25 +991,97 @@ class map_function : public function_expression { } }; +class take_while_function : public function_expression { +public: + explicit take_while_function(const args_list& args) + : function_expression("take_while", args, 2, 2) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const variant& items = args()[0]->evaluate(variables, fdb); + variant_iterator it = items.begin(); + for(; it != items.end(); ++it) { + const variant matches = args().back()->evaluate(formula_variant_callable_with_backup(*it, variables),fdb); + if (!matches.as_bool()) + break; + } + std::vector result(items.begin(), it); + return variant(&result); + } +}; + +class zip_function : public function_expression { +public: + explicit zip_function(const args_list& args) + : function_expression("zip", args, 1, -1) + {} +private: + struct indexer { + size_t i; + explicit indexer(size_t i) : i(i) {} + variant operator()(const variant& v) { + if(i >= v.num_elements()) { + return variant(); + } else { + return v[i]; + } + } + }; + struct comparator { + bool operator()(const variant& a, const variant& b) { + return a.num_elements() < b.num_elements(); + } + }; + std::vector get_input(const formula_callable& variables, formula_debugger* fdb) const { + if(args().size() == 1) { + const variant list = args()[0]->evaluate(variables, fdb); + return std::vector(list.begin(), list.end()); + } else { + std::vector input; + input.reserve(args().size()); + BOOST_FOREACH(expression_ptr expr, args()) { + input.push_back(expr->evaluate(variables, fdb)); + } + return input; + } + } + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const std::vector input = get_input(variables, fdb); + std::vector output; + // So basically this does [[a,b,c],[d,e,f],[x,y,z]] -> [[a,d,x],[b,e,y],[c,f,z]] + // Or [[a,b,c,d],[x,y,z]] -> [[a,x],[b,y],[c,z],[d,null()]] + size_t max_i = std::max_element(input.begin(), input.end(), comparator())->num_elements(); + output.reserve(max_i); + for(size_t i = 0; i < max_i; i++) { + std::vector elem(input.size()); + std::transform(input.begin(), input.end(), elem.begin(), indexer(i)); + output.push_back(variant(&elem)); + } + return variant(&output); + } +}; + class reduce_function : public function_expression { public: explicit reduce_function(const args_list& args) - : function_expression("reduce", args, 2, 2) + : function_expression("reduce", args, 2, 3) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { const variant items = args()[0]->evaluate(variables,fdb); + const variant initial = args().size() == 2 ? variant() : args()[1]->evaluate(variables,fdb); if(items.num_elements() == 0) - return variant(); - else if(items.num_elements() == 1) - return items[0]; + return initial; variant_iterator it = items.begin(); - variant res(*it); + variant res(initial.is_null() ? *it : initial); + if(res != initial) { + ++it; + } map_formula_callable self_callable; self_callable.add_ref(); - for(++it; it != items.end(); ++it) { + for(; it != items.end(); ++it) { self_callable.add("a", res); self_callable.add("b", *it); res = args().back()->evaluate(formula_callable_with_backup(self_callable, formula_variant_callable_with_backup(*it, variables)),fdb); @@ -862,7 +1138,7 @@ class sum_function : public function_expression { class head_function : public function_expression { public: explicit head_function(const args_list& args) - : function_expression("head", args, 1, 1) + : function_expression("head", args, 1, 2) {} private: variant execute(const formula_callable& variables, formula_debugger *fdb) const { @@ -871,7 +1147,40 @@ class head_function : public function_expression { if(it == items.end()) { return variant(); } - return *it; + if(args().size() == 1) { + return *it; + } + const int n = items.num_elements(), req = args()[1]->evaluate(variables,fdb).as_int(); + const int count = req < 0 ? n - std::min(-req, n) : std::min(req, n); + variant_iterator end = it; + std::advance(end, count); + std::vector res; + std::copy(it, end, std::back_inserter(res)); + return variant(&res); + } +}; + +class tail_function : public function_expression { +public: + explicit tail_function(const args_list& args) + : function_expression("tail", args, 1, 2) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const variant items = args()[0]->evaluate(variables,fdb); + variant_iterator it = items.end(); + if(it == items.begin()) { + return variant(); + } + if(args().size() == 1) { + return *--it; + } + const int n = items.num_elements(), req = args()[1]->evaluate(variables,fdb).as_int(); + const int count = req < 0 ? n - std::min(-req, n) : std::min(req, n); + std::advance(it, -count); + std::vector res; + std::copy(it, items.end(), std::back_inserter(res)); + return variant(&res); } }; @@ -1014,6 +1323,33 @@ class loc_function : public function_expression { } }; +class distance_between_function : public function_expression { +public: + explicit distance_between_function(const args_list& args) + : function_expression("distance_between", args, 2, 2) + {} + +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const map_location loc1 = convert_variant(args()[0]->evaluate(variables,add_debug_info(fdb,0,"distance_between:location_A")))->loc(); + const map_location loc2 = convert_variant(args()[1]->evaluate(variables,add_debug_info(fdb,1,"distance_between:location_B")))->loc(); + return variant(distance_between(loc1, loc2)); + } +}; + + +class type_function : public function_expression { +public: + explicit type_function(const args_list& args) + : function_expression("type", args, 1, 1) + {} +private: + variant execute(const formula_callable& variables, formula_debugger *fdb) const { + const variant& v = args()[0]->evaluate(variables, fdb); + return variant(v.type_string()); + } +}; + } variant key_value_pair::get_value(const std::string& key) const @@ -1162,12 +1498,17 @@ functions_map& get_functions_map() { FUNCTION(wave); FUNCTION(sort); FUNCTION(contains_string); + FUNCTION(find_string); + FUNCTION(reverse); FUNCTION(filter); FUNCTION(find); FUNCTION(map); + FUNCTION(zip); + FUNCTION(take_while); FUNCTION(reduce); FUNCTION(sum); FUNCTION(head); + FUNCTION(tail); FUNCTION(size); FUNCTION(null); FUNCTION(ceil); @@ -1176,16 +1517,30 @@ functions_map& get_functions_map() { FUNCTION(as_decimal); FUNCTION(refcount); FUNCTION(loc); + FUNCTION(distance_between); FUNCTION(index_of); FUNCTION(keys); FUNCTION(values); FUNCTION(tolist); FUNCTION(tomap); FUNCTION(substring); + FUNCTION(replace); FUNCTION(length); FUNCTION(concatenate); FUNCTION(sin); FUNCTION(cos); + FUNCTION(tan); + FUNCTION(asin); + FUNCTION(acos); + FUNCTION(atan); + FUNCTION(sqrt); + FUNCTION(cbrt); + FUNCTION(root); + FUNCTION(log); + FUNCTION(exp); + FUNCTION(pi); + FUNCTION(hypot); + FUNCTION(type); #undef FUNCTION } diff --git a/src/formula_tokenizer.cpp b/src/formula_tokenizer.cpp index b711172bea62..8fc6c373be1f 100644 --- a/src/formula_tokenizer.cpp +++ b/src/formula_tokenizer.cpp @@ -35,7 +35,7 @@ void raise_exception(iterator& i1, iterator i2, std::string str) { } -token get_token(iterator& i1, iterator i2) { +token get_token(iterator& i1, const iterator i2) { iterator it = i1; if( *i1 >= 'A' ) { @@ -52,13 +52,15 @@ token get_token(iterator& i1, iterator i2) { //check if this string matches any keyword or an operator //possible operators and keywords: - // d, or, def, and, not, fai, where, faiend, functions + // d, or, in, def, and, not, wfl, where, wflend, functions if( diff == 1 ) { if( *it == 'd' ) t = TOKEN_OPERATOR; } else if( diff == 2 ) { if( *it == 'o' && *(it+1) == 'r' ) t = TOKEN_OPERATOR; + else if( *it == 'i' && *(it+1) == 'n' ) + t = TOKEN_OPERATOR; } else if( diff == 3 ) { if( *it == 'd' ) { //def if( *(it+1) == 'e' && *(it+2) == 'f' ) @@ -72,6 +74,9 @@ token get_token(iterator& i1, iterator i2) { } else if( *it == 'f' ) { //fai if( *(it+1) == 'a' && *(it+2) == 'i' ) t = TOKEN_KEYWORD; + } else if( *it == 'w' ) { //wfl + if( *(it+1) == 'f' && *(it+2) == 'l' ) + t = TOKEN_KEYWORD; } } else if( diff == 5 ) { std::string s(it, i1); @@ -81,6 +86,8 @@ token get_token(iterator& i1, iterator i2) { std::string s(it, i1); if( s == "faiend" ) t = TOKEN_KEYWORD; + else if( s == "wflend" ) + t = TOKEN_KEYWORD; } else if( diff == 9 ) { std::string s(it, i1); if( s == "functions" ) @@ -99,6 +106,12 @@ token get_token(iterator& i1, iterator i2) { if( *i1 == '^' ) return token( it, ++i1, TOKEN_OPERATOR ); + if( *i1 == '~' ) + return token( it, ++i1, TOKEN_OPERATOR ); + + //unused characters in this range: + // \ ` { | } + // Note: {} should never be used since they play poorly with WML preprocessor } } else { //limit search to the lower-half of the ASCII table @@ -147,6 +160,8 @@ token get_token(iterator& i1, iterator i2) { //current character is between ':' and '@' //possible tokens at this point that we are interested in: // ; < = > <= >= + //unused characters in this range: + // : ? @ if( *i1 == ';' ) { return token( it, ++i1, TOKEN_SEMICOLON); @@ -173,6 +188,12 @@ token get_token(iterator& i1, iterator i2) { } } //current character is between '!' and '/' + //possible tokens: + // , . .+ .- .* ./ .. ( ) ' # + - -> * / % != + //unused characters: + // ! " $ & + // ! is used only as part of != + // Note: " should never be used since it plays poorly with WML } else if ( *i1 == ',' ) { return token( it, ++i1, TOKEN_COMMA); @@ -180,7 +201,7 @@ token get_token(iterator& i1, iterator i2) { ++i1; if( i1 != i2 ) { - if( *i1 == '+' || *i1 == '-' || *i1 == '*' || *i1 == '/') + if( *i1 == '+' || *i1 == '-' || *i1 == '*' || *i1 == '/' || *i1 == '.') return token( it, ++i1, TOKEN_OPERATOR ); else return token( it, i1, TOKEN_OPERATOR ); @@ -195,9 +216,18 @@ token get_token(iterator& i1, iterator i2) { return token( it, ++i1, TOKEN_RPARENS); } else if ( *i1 == '\'' ) { + int bracket_depth = 0; ++i1; - while( i1 != i2 && *i1 != '\'' ) + while (i1 != i2) { + if (*i1 == '[') { + bracket_depth++; + } else if(bracket_depth > 0 && *i1 == ']') { + bracket_depth--; + } else if(bracket_depth == 0 && *i1 == '\'') { + break; + } ++i1; + } if( i1 != i2 ) { return token( it, ++i1, TOKEN_STRING_LITERAL ); diff --git a/src/gui/dialogs/formula_debugger.cpp b/src/gui/dialogs/formula_debugger.cpp index e23dd4d894b2..65c05d4e2623 100644 --- a/src/gui/dialogs/formula_debugger.cpp +++ b/src/gui/dialogs/formula_debugger.cpp @@ -26,6 +26,27 @@ #include +namespace { + +std::string pango_escape(std::string str) { + for(size_t i = str.size(); i > 0; i--) { + if(str[i-1] == '<') { + str.replace(i-1, 1, "<"); + } else if(str[i-1] == '>') { + str.replace(i-1, 1, ">"); + } else if(str[i-1] == '&') { + str.replace(i-1, 1, "&"); + } else if(str[i-1] == '"') { + str.replace(i-1, 1, """); + } else if(str[i-1] == '\'') { + str.replace(i-1, 1, "'"); + } + } + return str; +} + +} + namespace gui2 { @@ -80,8 +101,8 @@ void tformula_debugger::pre_show(twindow& window) stack_text << indent; } stack_text << "#" << i.counter() - << ": \"" << i.name() - << "\": '" << i.str() << "' " << std::endl; + << ": \"" << pango_escape(i.name()) + << "\": (" << pango_escape(i.str()) << ") " << std::endl; ++c; } @@ -101,14 +122,14 @@ void tformula_debugger::pre_show(twindow& window) } if(!i.evaluated()) { execution_text << "#" << i.counter() - << ": \"" << i.name() - << "\": '" << i.str() << "' " << std::endl; + << ": \"" << pango_escape(i.name()) + << "\": (" << pango_escape(i.str()) << ") " << std::endl; } else { execution_text << "#" << i.counter() - << ": \"" << i.name() - << "\": '" << i.str() << "' = " - << "" - << i.value().to_debug_string(NULL, false) + << ": \"" << pango_escape(i.name()) + << "\": (" << pango_escape(i.str()) << ") = " + << "" + << pango_escape(i.value().to_debug_string(NULL, false)) << "" << std::endl; } } diff --git a/src/menu_events.cpp b/src/menu_events.cpp index 72313a7b2b1d..f9421d084628 100644 --- a/src/menu_events.cpp +++ b/src/menu_events.cpp @@ -3067,9 +3067,10 @@ void menu_handler::do_ai_formula(const std::string& str, int side_num, mouse_handler& /*mousehandler*/) { try { - add_chat_message(time(NULL), _("ai"), 0, ai::manager::evaluate_command(side_num, str)); + add_chat_message(time(NULL), _("wfl"), 0, ai::manager::evaluate_command(side_num, str)); + } catch(game_logic::formula_error&) { } catch(...) { - //add_chat_message(time(NULL), _("ai"), 0, "ERROR IN FORMULA"); + add_chat_message(time(NULL), _("wfl"), 0, "UNKNOWN ERROR IN FORMULA"); } } diff --git a/src/scripting/game_lua_kernel.cpp b/src/scripting/game_lua_kernel.cpp index 960f605dd7a9..19a896572a46 100644 --- a/src/scripting/game_lua_kernel.cpp +++ b/src/scripting/game_lua_kernel.cpp @@ -290,6 +290,7 @@ static int impl_unit_get(lua_State *L) return_string_attrib("id", u.id()); return_string_attrib("type", u.type_id()); return_string_attrib("image_mods", u.effect_image_mods()); + return_string_attrib("usage", u.usage()); return_int_attrib("hitpoints", u.hitpoints()); return_int_attrib("max_hitpoints", u.max_hitpoints()); return_int_attrib("experience", u.experience()); @@ -302,6 +303,7 @@ static int impl_unit_get(lua_State *L) return_tstring_attrib("name", u.name()); return_bool_attrib("canrecruit", u.can_recruit()); return_int_attrib("level", u.level()); + return_int_attrib("cost", u.cost()); return_vector_string_attrib("extra_recruit", u.recruits()); return_vector_string_attrib("advances_to", u.advances_to()); @@ -332,6 +334,14 @@ static int impl_unit_get(lua_State *L) lua_push(L, u.overlays()); return 1; } + if (strcmp(m, "traits") == 0) { + lua_push(L, u.get_ability_list()); + return 1; + } + if (strcmp(m, "abilities") == 0) { + lua_push(L, u.get_traits_list()); + return 1; + } if (strcmp(m, "status") == 0) { lua_createtable(L, 1, 0); lua_pushvalue(L, 1); diff --git a/src/tests/test_formula_core.cpp b/src/tests/test_formula_core.cpp new file mode 100644 index 000000000000..46063b79907c --- /dev/null +++ b/src/tests/test_formula_core.cpp @@ -0,0 +1,180 @@ +/* + Copyright (C) 2008 - 2016 + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#define GETTEXT_DOMAIN "wesnoth-test" + +#include + +#include + +#include "formula.hpp" +#include "formula_callable.hpp" + +using namespace game_logic; + +class mock_char : public formula_callable { + variant get_value(const std::string& key) const { + if(key == "strength") { + return variant(15); + } else if(key == "agility") { + return variant(12); + } + + return variant(10); + } +}; + +class mock_party : public formula_callable { + variant get_value(const std::string& key) const { + if(key == "members") { + i_[0].add("strength",variant(12)); + i_[1].add("strength",variant(16)); + i_[2].add("strength",variant(14)); + std::vector members; + for(int n = 0; n != 3; ++n) { + members.push_back(variant(&i_[n])); + } + + return variant(&members); + } else if(key == "char") { + return variant(&c_); + } else { + return variant(0); + } + } + + mock_char c_; + mutable map_formula_callable i_[3]; + +public: + mock_party() { + c_.add_ref(); + i_[0].add_ref(); + i_[1].add_ref(); + i_[2].add_ref(); + } +}; + +BOOST_AUTO_TEST_SUITE(formula_core) + +mock_char c; +mock_party p; + +BOOST_AUTO_TEST_CASE(test_formula_basic_arithmetic) +{ + BOOST_CHECK_EQUAL(formula("strength").evaluate(c).as_int(), 15); + BOOST_CHECK_EQUAL(formula("17").evaluate().as_int(), 17); + + BOOST_CHECK_EQUAL(formula("strength/2 + agility").evaluate(c).as_int(), 19); + BOOST_CHECK_EQUAL(formula("(strength+agility)/2").evaluate(c).as_int(), 13); + + BOOST_CHECK_EQUAL(formula("20 % 3").evaluate().as_int(), 2); + BOOST_CHECK_EQUAL(formula("19.5 % 3").evaluate().as_decimal(), + static_cast(1000.0 * 1.5)); + + BOOST_CHECK_EQUAL(formula("-5").evaluate().as_int(), -5); + + BOOST_CHECK_EQUAL(formula("4^2").evaluate().as_int(), 16); + BOOST_CHECK_EQUAL(formula("2+3^3").evaluate().as_int(), 29); + BOOST_CHECK_EQUAL(formula("2*3^3+2").evaluate().as_int(), 56); + BOOST_CHECK_EQUAL(formula("2^3^2").evaluate().as_int(), 512); + BOOST_CHECK_EQUAL(formula("9^3").evaluate().as_int(), 729); + BOOST_CHECK(formula("(-2)^0.5").evaluate().is_null()); +} + +BOOST_AUTO_TEST_CASE(test_formula_basic_logic) +{ + BOOST_CHECK_EQUAL(formula("strength > 12").evaluate(c).as_int(), 1); + BOOST_CHECK_EQUAL(formula("strength > 18").evaluate(c).as_int(), 0); + + BOOST_CHECK_EQUAL(formula("if(strength > 12, 7, 2)").evaluate(c).as_int(), 7); + BOOST_CHECK_EQUAL(formula("if(strength > 18, 7, 2)").evaluate(c).as_int(), 2); + + BOOST_CHECK_EQUAL(formula("2 and 1").evaluate().as_int(), 1); + BOOST_CHECK_EQUAL(formula("2 and 0").evaluate().as_int(), 0); + BOOST_CHECK_EQUAL(formula("2 or 0").evaluate().as_int(), 2); + + BOOST_CHECK_EQUAL(formula("not 5").evaluate().as_int(), 0); + BOOST_CHECK_EQUAL(formula("not 0").evaluate().as_int(), 1); +} + +BOOST_AUTO_TEST_CASE(test_formula_callable) +{ + // These are just misc tests that were in the original unit tests + // I wasn't sure how to classify them. + BOOST_CHECK_EQUAL(formula("char.strength").evaluate(p).as_int(), 15); + BOOST_CHECK_EQUAL(formula("choose(members,strength).strength").evaluate(p).as_int(), 16); + + BOOST_CHECK_EQUAL(formula("char.sum([strength, agility, intelligence])").evaluate(p).as_int(), 37); +} + +BOOST_AUTO_TEST_CASE(test_formula_where_clause) +{ + BOOST_CHECK_EQUAL(formula("x*5 where x=1").evaluate().as_int(), 5); + BOOST_CHECK_EQUAL(formula("x*5 where x=2").evaluate().as_int(), 10); + + BOOST_CHECK_EQUAL(formula("x*(a*b where a=2,b=1) where x=5").evaluate().as_int(), 10); + BOOST_CHECK_EQUAL(formula("char.strength * ability where ability=3").evaluate(p).as_int(), 45); +} + +BOOST_AUTO_TEST_CASE(test_formula_strings) +{ + BOOST_CHECK_EQUAL(formula("'abcd' = 'abcd'").evaluate().as_bool(), true); + BOOST_CHECK_EQUAL(formula("'abcd' = 'acd'").evaluate().as_bool(), false); + + BOOST_CHECK_EQUAL(formula("'ab' .. 'cd'").evaluate().as_string(), "abcd"); + + BOOST_CHECK_EQUAL(formula("'strength, agility: [strength], [agility]'").evaluate(c).as_string(), + "strength, agility: 15, 12"); + + BOOST_CHECK_EQUAL(formula("'String with [']quotes['] and [(]brackets[)]!'").evaluate().as_string(), + "String with 'quotes' and [brackets]!"); + BOOST_CHECK_EQUAL(formula("'String with ['embedded ' .. 'string']!'").evaluate().as_string(), + "String with embedded string!"); +} + +BOOST_AUTO_TEST_CASE(test_formula_dice) { + const int dice_roll = formula("3d6").evaluate().as_int(); + assert(dice_roll >= 3 && dice_roll <= 18); +} + +BOOST_AUTO_TEST_CASE(test_formula_containers) { + variant myarray = formula("[1,2,3]").evaluate(); + BOOST_CHECK_EQUAL(myarray.num_elements(), 3); + BOOST_CHECK_EQUAL(myarray[0].as_int(), 1); + BOOST_CHECK_EQUAL(myarray[1].as_int(), 2); + BOOST_CHECK_EQUAL(myarray[2].as_int(), 3); + + variant mydict = formula("['foo' -> 5, 'bar' ->7]").evaluate(); + BOOST_CHECK_EQUAL(mydict.num_elements(), 2); + BOOST_CHECK_EQUAL(mydict[variant("foo")].as_int(), 5); + BOOST_CHECK_EQUAL(mydict[variant("bar")].as_int(), 7); + + variant myrange = formula("-2~2").evaluate(); + BOOST_CHECK_EQUAL(myrange.num_elements(), 5); + BOOST_CHECK_EQUAL(myrange[0].as_int(), -2); + BOOST_CHECK_EQUAL(myrange[1].as_int(), -1); + BOOST_CHECK_EQUAL(myrange[2].as_int(), 0); + BOOST_CHECK_EQUAL(myrange[3].as_int(), 1); + BOOST_CHECK_EQUAL(myrange[4].as_int(), 2); + + variant myslice = formula("(10~20)[[1,3,7,9]]").evaluate(); + BOOST_CHECK_EQUAL(myslice.num_elements(), 4); + BOOST_CHECK_EQUAL(myslice[0].as_int(), 11); + BOOST_CHECK_EQUAL(myslice[1].as_int(), 13); + BOOST_CHECK_EQUAL(myslice[2].as_int(), 17); + BOOST_CHECK_EQUAL(myslice[3].as_int(), 19); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/test_formula_function.cpp b/src/tests/test_formula_function.cpp index 8221cab65e04..ffcbed5a66ed 100644 --- a/src/tests/test_formula_function.cpp +++ b/src/tests/test_formula_function.cpp @@ -21,6 +21,8 @@ #include +using namespace game_logic; + BOOST_AUTO_TEST_SUITE(formula_function) BOOST_AUTO_TEST_CASE(test_formula_function_substring) @@ -73,12 +75,10 @@ BOOST_AUTO_TEST_CASE(test_formula_function_substring) .evaluate().as_string() , ""); - lg::set_log_domain_severity("scripting/formula", lg::err.get_severity() - 1); // Don't log anything - BOOST_CHECK_EQUAL( game_logic::formula("substring('hello world', 0, -1)") .evaluate().as_string() - , ""); + , "h"); lg::set_log_domain_severity("scripting/formula", lg::debug); @@ -97,6 +97,16 @@ BOOST_AUTO_TEST_CASE(test_formula_function_substring) .evaluate().as_string() , "ello worl"); + BOOST_CHECK_EQUAL( + game_logic::formula("substring('hello world', -1, -5)") + .evaluate().as_string() + , "world"); + + BOOST_CHECK_EQUAL( + game_logic::formula("substring('hello world', 4, -5)") + .evaluate().as_string() + , "hello"); + } BOOST_AUTO_TEST_CASE(test_formula_function_length) @@ -133,7 +143,30 @@ BOOST_AUTO_TEST_CASE(test_formula_function_concatenate) , "1.000, 1.000, 1.000, 1.200, 1.230, 1.234"); } -BOOST_AUTO_TEST_CASE(test_formula_function_sin_cos) +BOOST_AUTO_TEST_CASE(test_formula_function_math) +{ + BOOST_CHECK_EQUAL(formula("abs(5)").evaluate().as_int(), 5); + BOOST_CHECK_EQUAL(formula("abs(-5)").evaluate().as_int(), 5); + BOOST_CHECK_EQUAL(formula("abs(5.0)").evaluate().as_int(), 5); + BOOST_CHECK_EQUAL(formula("abs(-5.0)").evaluate().as_int(), 5); + + BOOST_CHECK_EQUAL(formula("min(3,5)").evaluate().as_int(), 3); + BOOST_CHECK_EQUAL(formula("min(5,2)").evaluate().as_int(), 2); + BOOST_CHECK_EQUAL(formula("max(3,5)").evaluate().as_int(), 5); + BOOST_CHECK_EQUAL(formula("max(5,2)").evaluate().as_int(), 5); + BOOST_CHECK_EQUAL(formula("max(5.5,5)").evaluate().as_decimal(), + static_cast(1000.0 * 5.5)); + + BOOST_CHECK_EQUAL(formula("max(4,5,[2,18,7])").evaluate().as_int(), 18); + + BOOST_CHECK_EQUAL(formula("log(8,2)").evaluate().as_int(), 3); + BOOST_CHECK_EQUAL(formula("log(12)").evaluate().as_decimal(), + static_cast(round(1000.0 * log(12)))); + BOOST_CHECK_EQUAL(formula("exp(3)").evaluate().as_decimal(), + static_cast(round(1000.0 * exp(3)))); +} + +BOOST_AUTO_TEST_CASE(test_formula_function_trig) { const double pi = 4. * atan(1.); @@ -145,12 +178,23 @@ BOOST_AUTO_TEST_CASE(test_formula_function_sin_cos) BOOST_CHECK_EQUAL( game_logic::formula("sin(x)") .evaluate(variables).as_decimal() - , static_cast(1000. * sin(x * pi / 180.))); + , static_cast(round(1000. * sin(x * pi / 180.)))); BOOST_CHECK_EQUAL( game_logic::formula("cos(x)") .evaluate(variables).as_decimal() - , static_cast(1000. * cos(x * pi / 180.))); + , static_cast(round(1000. * cos(x * pi / 180.)))); + + if(x % 90 == 0 && x % 180 != 0) { + BOOST_CHECK( + game_logic::formula("tan(x)") + .evaluate(variables).is_null()); + } else { + BOOST_CHECK_EQUAL( + game_logic::formula("tan(x)") + .evaluate(variables).as_decimal(), + static_cast(round(1000. * tan(x * pi / 180.)))); + } } } diff --git a/src/unit_formula_manager.cpp b/src/unit_formula_manager.cpp index 4c7fcdfdbe72..7f20c911aba3 100644 --- a/src/unit_formula_manager.cpp +++ b/src/unit_formula_manager.cpp @@ -19,17 +19,24 @@ #include "formula.hpp" #include "formula_string_utils.hpp" #include "map_location.hpp" +#include "log.hpp" #include bool unit_formula_manager::matches_filter(const std::string & cfg_formula, const map_location & loc, const unit & me) { - const unit_callable callable(loc,me); - const game_logic::formula form(cfg_formula); - if(!form.evaluate(callable).as_bool()) {///@todo use formula_ai + try { + const unit_callable callable(loc,me); + const game_logic::formula form(cfg_formula); + if(!form.evaluate(callable).as_bool()) {///@todo use formula_ai + return false; + } + return true; + } catch(game_logic::formula_error& e) { + lg::wml_error << "Formula error in unit filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n"; + // Formulae with syntax errors match nothing return false; } - return true; } void unit_formula_manager::add_formula_var(std::string str, variant var) diff --git a/src/variant.cpp b/src/variant.cpp index 9c3a1e968a24..ffef0356b7ab 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -31,7 +31,7 @@ std::string variant_type_to_string(variant::TYPE type) { case variant::TYPE_NULL: return "null"; case variant::TYPE_INT: - return "int"; + return "integer"; case variant::TYPE_DECIMAL: return "decimal"; case variant::TYPE_CALLABLE: @@ -164,6 +164,33 @@ variant_iterator variant_iterator::operator++(int) return iter; } +variant_iterator& variant_iterator::operator--() +{ + if (type_ == TYPE_LIST) + { + --list_iterator_; + } else if (type_ == TYPE_MAP) + { + --map_iterator_; + } + + return *this; +} + +variant_iterator variant_iterator::operator--(int) +{ + variant_iterator iter(*this); + if (type_ == TYPE_LIST) + { + --list_iterator_; + } else if (type_ == TYPE_MAP) + { + --map_iterator_; + } + + return iter; +} + variant_iterator& variant_iterator::operator=(const variant_iterator& that) { if (this == &that) @@ -309,6 +336,10 @@ void variant::release() } } +std::string variant::type_string() const { + return variant_type_to_string(type_); +} + variant::variant() : type_(TYPE_NULL), int_value_(0) {} @@ -318,6 +349,18 @@ variant::variant(int n) : type_(TYPE_INT), int_value_(n) variant::variant(int n, variant::DECIMAL_VARIANT_TYPE /*type*/) : type_(TYPE_DECIMAL), decimal_value_(n) {} +variant::variant(double n, variant::DECIMAL_VARIANT_TYPE /*type*/) : type_(TYPE_DECIMAL) { + n *= 1000; + decimal_value_ = static_cast(n); + + n -= decimal_value_; + + if(n > 0.5) + decimal_value_++; + else if(n < -0.5) + decimal_value_--; +} + variant::variant(const game_logic::formula_callable* callable) : type_(TYPE_CALLABLE), callable_(callable) { @@ -373,10 +416,9 @@ variant& variant::operator=(const variant& v) return *this; } -const variant& variant::operator[](size_t n) const +variant variant::operator[](size_t n) const { if(type_ == TYPE_CALLABLE) { - assert(n == 0); return *this; } @@ -389,10 +431,9 @@ const variant& variant::operator[](size_t n) const return list_->elements[n]; } -const variant& variant::operator[](const variant& v) const +variant variant::operator[](const variant& v) const { if(type_ == TYPE_CALLABLE) { - assert(v.as_int() == 0); return *this; } @@ -406,11 +447,20 @@ const variant& variant::operator[](const variant& v) const } return i->second; } else if(type_ == TYPE_LIST) { + if(v.is_list()) { + std::vector slice; + + for(size_t i = 0; i < v.num_elements(); ++i) { + slice.push_back( (*this)[v[i]] ); + } + return variant(&slice); + } else if(v.as_int() < 0) { + return operator[](num_elements() + v.as_int()); + } return operator[](v.as_int()); } else { throw type_error((formatter() << "type error: " - << " expected a list or a map but found " - << variant_type_to_string(type_) + << " expected a list or a map but found " << type_string() << " (" << to_debug_string() << ")").str()); } } @@ -487,8 +537,7 @@ size_t variant::num_elements() const return map_->elements.size(); } else { throw type_error((formatter() << "type error: " - << " expected a list or a map but found " - << variant_type_to_string(type_) + << " expected a list or a map but found " << type_string() << " (" << to_debug_string() << ")").str()); } } @@ -506,6 +555,13 @@ variant variant::get_member(const std::string& str) const } } +int variant::as_int() const { + if(type_ == TYPE_NULL) { return 0; } + if(type_ == TYPE_DECIMAL) { return as_decimal() / 1000; } + must_be(TYPE_INT); + return int_value_; +} + int variant::as_decimal() const { if( type_ == TYPE_DECIMAL) { @@ -516,8 +572,7 @@ int variant::as_decimal() const return 0; } else { throw type_error((formatter() << "type error: " - << " expected integer or decimal but found " - << variant_type_to_string(type_) + << " expected integer or decimal but found " << type_string() << " (" << to_debug_string() << ")").str()); } } @@ -552,6 +607,20 @@ const std::string& variant::as_string() const return string_->str; } +const std::vector& variant::as_list() const +{ + must_be(TYPE_LIST); + assert(list_); + return list_->elements; +} + +const std::map& variant::as_map() const +{ + must_be(TYPE_MAP); + assert(map_); + return map_->elements; +} + variant variant::operator+(const variant& v) const { if(type_ == TYPE_LIST) { @@ -656,13 +725,23 @@ variant variant::operator/(const variant& v) const variant variant::operator%(const variant& v) const { - const int numerator = as_int(); - const int denominator = v.as_int(); - if(denominator == 0) { - throw type_error((formatter() << "divide by zero error").str()); - } + if(type_ == TYPE_DECIMAL || v.type_ == TYPE_DECIMAL) { + const int numerator = as_decimal(); + const int denominator = v.as_decimal(); + if(denominator == 0) { + throw type_error((formatter() << "divide by zero error").str()); + } + + return variant(numerator%denominator, DECIMAL_VARIANT); + } else { + const int numerator = as_int(); + const int denominator = v.as_int(); + if(denominator == 0) { + throw type_error((formatter() << "divide by zero error").str()); + } - return variant(numerator%denominator); + return variant(numerator%denominator); + } } @@ -671,16 +750,10 @@ variant variant::operator^(const variant& v) const if( type_ == TYPE_DECIMAL || v.type_ == TYPE_DECIMAL ) { double res = pow( as_decimal()/1000.0 , v.as_decimal()/1000.0 ); + + if(res != res) return variant(); - res *= 1000; - int i = static_cast( res ); - - res -= i; - - if( res > 0.5 ) - i++; - - return variant( i , variant::DECIMAL_VARIANT); + return variant(res, DECIMAL_VARIANT); } return variant(static_cast( @@ -745,7 +818,6 @@ bool variant::operator==(const variant& v) const } } - assert(false); return false; } @@ -757,7 +829,10 @@ bool variant::operator!=(const variant& v) const bool variant::operator<=(const variant& v) const { if(type_ != v.type_) { - if( type_ == TYPE_DECIMAL || v.type_ == TYPE_DECIMAL ) { + if(type_ == TYPE_DECIMAL && v.type_ == TYPE_INT) { + return as_decimal() <= v.as_decimal(); + } + if(v.type_ == TYPE_DECIMAL && type_ == TYPE_INT) { return as_decimal() <= v.as_decimal(); } @@ -893,12 +968,76 @@ variant variant::list_elements_div(const variant& v) const return variant( &res ); } +variant variant::concatenate(const variant& v) const +{ + if(type_ == TYPE_LIST) { + v.must_be(TYPE_LIST); + + std::vector< variant > res; + res.reserve(num_elements() + v.num_elements()); + + for(size_t i = 0; i < num_elements(); ++i) { + res.push_back( (*this)[i] ); + } + + for(size_t i = 0; i < v.num_elements(); ++i) { + res.push_back( v[i] ); + } + + return variant( &res ); + } else if(type_ == TYPE_STRING) { + v.must_be(TYPE_STRING); + std::string res = as_string() + v.as_string(); + return variant( res ); + } else { + throw type_error((formatter() << "type error: expected two " + << " lists or two maps but found " << type_string() + << " (" << to_debug_string() << ")" + << " and " << v.type_string() + << " (" << v.to_debug_string() << ")").str()); + } +} + +variant variant::build_range(const variant& v) const { + must_be(TYPE_INT); + v.must_be(TYPE_INT); + + int lhs = as_int(), rhs = v.as_int(); + int len = abs(rhs - lhs) + 1; + + std::vector< variant > res; + res.reserve(len); + + for(size_t i = lhs; res.size() != res.capacity(); lhs < rhs ? ++i : --i) { + res.push_back( variant(i) ); + } + + return variant( &res ); +} + +bool variant::contains(const variant& v) const { + if(type_ != TYPE_LIST && type_ != TYPE_MAP) { + throw type_error((formatter() << "type error: expected " + << variant_type_to_string(TYPE_LIST) << " or " + << variant_type_to_string(TYPE_MAP) << " but found " + << variant_type_to_string(type_) + << " (" << to_debug_string() << ")").str()); + } + + if(type_ == TYPE_LIST) { + variant_iterator iter = std::find(begin(), end(), v); + return iter != end(); + } else { + std::map::const_iterator iter = map_->elements.find(v); + return iter != map_->elements.end(); + } +} + void variant::must_be(variant::TYPE t) const { if(type_ != t) { throw type_error((formatter() << "type error: " << " expected " - << variant_type_to_string(t) << " but found " - << variant_type_to_string(type_) + << variant_type_to_string(t) << " but found " << type_string() << " (" << to_debug_string() << ")").str()); } } @@ -963,12 +1102,30 @@ void variant::serialize_to_string(std::string& str) const str += "->"; i->second.serialize_to_string(str); } + if(map_->elements.empty()) { + str += "->"; + } str += "]"; break; } case TYPE_STRING: str += "'"; - str += string_->str; + for(std::string::iterator it = string_->str.begin(); it < string_->str.end(); ++it) { + switch(*it) { + case '\'': + str += "[']"; + break; + case '[': + str += "[(]"; + break; + case ']': + str += "[)]"; + break; + default: + str += *it; + break; + } + } str += "'"; break; default: diff --git a/src/variant.hpp b/src/variant.hpp index 133482f979b4..f5b64cf88cb0 100644 --- a/src/variant.hpp +++ b/src/variant.hpp @@ -59,6 +59,7 @@ class variant { variant(); explicit variant(int n); variant(int n, DECIMAL_VARIANT_TYPE /*type*/); + variant(double n, DECIMAL_VARIANT_TYPE /*type*/); explicit variant(const game_logic::formula_callable* callable); explicit variant(std::vector* array); explicit variant(const std::string& str); @@ -68,8 +69,8 @@ class variant { variant(const variant& v); variant& operator=(const variant& v); - const variant& operator[](size_t n) const; - const variant& operator[](const variant& v) const; + variant operator[](size_t n) const; + variant operator[](const variant& v) const; size_t num_elements() const; bool is_empty() const; @@ -80,7 +81,7 @@ class variant { bool is_int() const { return type_ == TYPE_INT; } bool is_decimal() const { return type_ == TYPE_DECIMAL; } bool is_map() const { return type_ == TYPE_MAP; } - int as_int() const { if(type_ == TYPE_NULL) { return 0; } must_be(TYPE_INT); return int_value_; } + int as_int() const; //this function returns variant's internal representation of decimal number: //for example number 1.234 is represented as 1234 @@ -89,8 +90,12 @@ class variant { bool as_bool() const; bool is_list() const { return type_ == TYPE_LIST; } + + const std::vector& as_list() const; + const std::map& as_map() const; const std::string& as_string() const; + std::string type_string() const; bool is_callable() const { return type_ == TYPE_CALLABLE; } const game_logic::formula_callable* as_callable() const { @@ -136,6 +141,9 @@ class variant { variant list_elements_sub(const variant& v) const; variant list_elements_mul(const variant& v) const; variant list_elements_div(const variant& v) const; + variant concatenate(const variant& v) const; + variant build_range(const variant& v) const; + bool contains(const variant& other) const; variant get_keys() const; variant get_values() const; @@ -179,6 +187,12 @@ class variant { */ class variant_iterator { public: + typedef variant value_type; + typedef std::bidirectional_iterator_tag iterator_category; + typedef variant& reference; + typedef variant* pointer; + typedef int difference_type; + /** * Constructor for a TYPE_NULL variant. */ @@ -207,6 +221,8 @@ class variant_iterator { variant operator*() const; variant_iterator& operator++(); variant_iterator operator++(int); + variant_iterator& operator--(); + variant_iterator operator--(int); variant_iterator& operator=(const variant_iterator& that); bool operator==(const variant_iterator& that) const; bool operator!=(const variant_iterator& that) const; diff --git a/wml_test_schedule b/wml_test_schedule index dfd008841617..cd7a23d8a9a6 100644 --- a/wml_test_schedule +++ b/wml_test_schedule @@ -147,6 +147,8 @@ 0 filter_this_unit_wml 0 filter_this_unit_tl 0 filter_this_unit_fai +0 filter_fai_unit +1 filter_fai_unit_error # Interrupt tag tests 0 check_interrupts_break 0 check_interrupts_return