Merge pull request #624 from CelticMinstrel/lua_formula_bridge
Many new features in the formula engine
This commit is contained in:
commit
bb510a58af
26 changed files with 1494 additions and 359 deletions
71
changelog
71
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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
)}
|
||||
|
|
|
@ -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 = "<group>"; };
|
||||
91DCA68B1C9066EC0030F8D0 /* unit_recruit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unit_recruit.cpp; sourceTree = "<group>"; };
|
||||
91DCA68C1C9066EC0030F8D0 /* unit_recruit.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_recruit.hpp; sourceTree = "<group>"; };
|
||||
91DCA68F1C9360610030F8D0 /* test_formula_core.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test_formula_core.cpp; sourceTree = "<group>"; };
|
||||
91ECD5D01BA11A5200B25CF1 /* unit_creator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unit_creator.cpp; sourceTree = "<group>"; };
|
||||
91ECD5D11BA11A5200B25CF1 /* unit_creator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = unit_creator.hpp; sourceTree = "<group>"; };
|
||||
91ECD5D31BA11A6400B25CF1 /* mp_replay_controller.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mp_replay_controller.cpp; sourceTree = "<group>"; };
|
||||
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"distance_between:location_A")))->loc();
|
||||
const map_location loc2 = convert_variant<location_callable>(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") {
|
||||
|
|
|
@ -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<std::string> traits = u_.get_traits_list();
|
||||
std::vector<variant> 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<std::string> recruits = u_.recruits();
|
||||
std::vector<variant> res;
|
||||
|
||||
if(recruits.empty())
|
||||
return variant( &res );
|
||||
|
||||
for (std::vector<std::string>::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<std::string> advances = u_.advances_to();
|
||||
std::vector<variant> res;
|
||||
|
||||
if(advances.empty())
|
||||
return variant( &res );
|
||||
|
||||
for (std::vector<std::string>::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<std::string, std::string>& 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<game_logic::formula_input>* 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<game_logic::formula_input>* 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));
|
||||
}
|
||||
|
||||
|
|
474
src/formula.cpp
474
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 <boost/foreach.hpp>
|
||||
|
||||
|
@ -103,7 +105,7 @@ private:
|
|||
std::vector<variant> res;
|
||||
res.reserve(items_.size());
|
||||
for(std::vector<expression_ptr>::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 @@ public:
|
|||
std::stringstream s;
|
||||
s << " [";
|
||||
for(std::vector<expression_ptr>::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 @@ private:
|
|||
variant execute(const formula_callable& variables, formula_debugger *fdb) const {
|
||||
std::map<variant,variant> res;
|
||||
for(std::vector<expression_ptr>::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 @@ public:
|
|||
|
||||
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 @@ private:
|
|||
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<formula_input>* 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<variant> 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<variant> 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<std::string> split = utils::parenthetical_split(string_.as_string(), ',');
|
||||
std::vector<variant> 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 @@ public:
|
|||
|
||||
};
|
||||
|
||||
class map_callable : public formula_callable {
|
||||
variant map_;
|
||||
public:
|
||||
explicit map_callable(const variant& map) : map_(map)
|
||||
{}
|
||||
|
||||
void get_inputs(std::vector<formula_input>* 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 @@ public:
|
|||
}
|
||||
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 @@ public:
|
|||
}
|
||||
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 @@ public:
|
|||
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 @@ private:
|
|||
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 @@ private:
|
|||
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<std::string, variant> 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 @@ private:
|
|||
const formula_callable& base_;
|
||||
expr_table_ptr table_;
|
||||
mutable exp_table_evaluated evaluated_table_;
|
||||
formula_debugger* debugger_;
|
||||
|
||||
void get_inputs(std::vector<formula_input>* inputs) const {
|
||||
for(expr_table::const_iterator i = table_->begin(); i != table_->end(); ++i) {
|
||||
|
@ -448,7 +577,7 @@ private:
|
|||
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 @@ private:
|
|||
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 @@ public:
|
|||
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 @@ public:
|
|||
|
||||
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 @@ private:
|
|||
} 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<expression_ptr> 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<expression_ptr>()));
|
||||
}
|
||||
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<expression_ptr> 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<token> 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<variant> 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 <time.h>
|
||||
|
||||
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
|
||||
|
|
|
@ -59,6 +59,7 @@ public:
|
|||
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 @@ private:
|
|||
{}
|
||||
expression_ptr expr_;
|
||||
std::string str_;
|
||||
function_symbol_table* symbols_;
|
||||
bool managing_symbols;
|
||||
friend class formula_debugger;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -81,7 +81,7 @@ public:
|
|||
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 @@ public:
|
|||
|
||||
//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 @@ private:
|
|||
std::deque< breakpoint_ptr > breakpoints_;
|
||||
std::deque<debug_info> execution_trace_;
|
||||
int arg_number_extra_debug_info;
|
||||
const char *f_name_extra_debug_info;
|
||||
std::string f_name_extra_debug_info;
|
||||
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -41,7 +41,7 @@ class base_breakpoint;
|
|||
|
||||
typedef boost::shared_ptr<base_breakpoint> 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);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "formula_debugger.hpp"
|
||||
#include "formula_function.hpp"
|
||||
#include "game_display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
@ -113,7 +114,7 @@ private:
|
|||
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 @@ public:
|
|||
|
||||
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 @@ public:
|
|||
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 @@ public:
|
|||
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 @@ private:
|
|||
{
|
||||
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 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
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 @@ public:
|
|||
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<size_t>(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 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class length_function
|
||||
: public function_expression {
|
||||
class replace_function : public function_expression {
|
||||
public:
|
||||
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<size_t>(offset) >= result.size()) {
|
||||
return variant(result);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class length_function : public function_expression {
|
||||
public:
|
||||
explicit length_function(const args_list& args)
|
||||
: function_expression("length", args, 1, 1)
|
||||
{}
|
||||
|
||||
variant execute(const formula_callable& variables
|
||||
, formula_debugger *fdb) const {
|
||||
|
||||
return variant(
|
||||
args()[0]->evaluate(variables, fdb).as_string().length());
|
||||
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 {
|
||||
variant execute(const formula_callable& variables, formula_debugger *fdb) const {
|
||||
std::string result;
|
||||
|
||||
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.;
|
||||
|
||||
return variant(
|
||||
static_cast<int>(1000. * sin(angle * pi<double>() / 180.))
|
||||
, variant::DECIMAL_VARIANT);
|
||||
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<double>() / 180.0);
|
||||
return variant(result, variant::DECIMAL_VARIANT);
|
||||
}
|
||||
};
|
||||
|
||||
class cos_function
|
||||
: public function_expression {
|
||||
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<double>() / 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 {
|
||||
const double angle = args()[0]->evaluate(variables,fdb).as_decimal() / 1000.0;
|
||||
const double result = tan(angle * pi<double>() / 180.0);
|
||||
if(result != result || result <= INT_MIN || result >= INT_MAX) {
|
||||
return variant();
|
||||
}
|
||||
return variant(result, variant::DECIMAL_VARIANT);
|
||||
}
|
||||
};
|
||||
|
||||
class asin_function : public function_expression {
|
||||
public:
|
||||
explicit asin_function(const args_list& args)
|
||||
: function_expression("asin", 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 = asin(num) * 180.0 / pi<double>();
|
||||
if(result != result) {
|
||||
return variant();
|
||||
}
|
||||
return variant(result, variant::DECIMAL_VARIANT);
|
||||
}
|
||||
};
|
||||
|
||||
const double angle =
|
||||
args()[0]->evaluate(variables,fdb).as_decimal() / 1000.;
|
||||
class acos_function : public function_expression {
|
||||
public:
|
||||
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<double>();
|
||||
if(result != result) {
|
||||
return variant();
|
||||
}
|
||||
return variant(result, variant::DECIMAL_VARIANT);
|
||||
}
|
||||
};
|
||||
|
||||
return variant(
|
||||
static_cast<int>(1000. * cos(angle * pi<double>() / 180.))
|
||||
, 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<double>();
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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<double>(), 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 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
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<variant> 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 @@ private:
|
|||
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);
|
||||
return variant(str.find(key) != std::string::npos);
|
||||
}
|
||||
};
|
||||
|
||||
std::string::iterator str_it, key_it, tmp_it;
|
||||
class find_string_function : public function_expression {
|
||||
public:
|
||||
explicit find_string_function(const args_list& args)
|
||||
: function_expression("find_string", args, 2, 2)
|
||||
{}
|
||||
|
||||
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;
|
||||
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();
|
||||
|
||||
while( *tmp_it == *key_it)
|
||||
{
|
||||
if( ++key_it == key.end())
|
||||
return variant(1);
|
||||
if( ++tmp_it == str.end())
|
||||
return variant(0);
|
||||
}
|
||||
}
|
||||
|
||||
return variant(0);
|
||||
size_t pos = str.find(key);
|
||||
return variant(static_cast<int>(pos));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -787,25 +991,97 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
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<variant> 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<variant> 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<variant>(list.begin(), list.end());
|
||||
} else {
|
||||
std::vector<variant> 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<variant> input = get_input(variables, fdb);
|
||||
std::vector<variant> 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<variant> 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 @@ private:
|
|||
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 @@ private:
|
|||
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<variant> 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<variant> res;
|
||||
std::copy(it, items.end(), std::back_inserter(res));
|
||||
return variant(&res);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1014,6 +1323,33 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
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<location_callable>(args()[0]->evaluate(variables,add_debug_info(fdb,0,"distance_between:location_A")))->loc();
|
||||
const map_location loc2 = convert_variant<location_callable>(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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -26,6 +26,27 @@
|
|||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
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 << "#<span color=\"green\">" << i.counter()
|
||||
<< "</span>: \"<span color=\"green\">" << i.name()
|
||||
<< "</span>\": '" << i.str() << "' " << std::endl;
|
||||
<< "</span>: \"<span color=\"green\">" << pango_escape(i.name())
|
||||
<< "</span>\": (" << pango_escape(i.str()) << ") " << std::endl;
|
||||
++c;
|
||||
}
|
||||
|
||||
|
@ -101,14 +122,14 @@ void tformula_debugger::pre_show(twindow& window)
|
|||
}
|
||||
if(!i.evaluated()) {
|
||||
execution_text << "#<span color=\"green\">" << i.counter()
|
||||
<< "</span>: \"<span color=\"green\">" << i.name()
|
||||
<< "</span>\": '" << i.str() << "' " << std::endl;
|
||||
<< "</span>: \"<span color=\"green\">" << pango_escape(i.name())
|
||||
<< "</span>\": (" << pango_escape(i.str()) << ") " << std::endl;
|
||||
} else {
|
||||
execution_text << "#<span color=\"yellow\">" << i.counter()
|
||||
<< "</span>: \"<span color=\"yellow\">" << i.name()
|
||||
<< "</span>\": '" << i.str() << "' = "
|
||||
<< "<span color=\"red\">"
|
||||
<< i.value().to_debug_string(NULL, false)
|
||||
<< "</span>: \"<span color=\"yellow\">" << pango_escape(i.name())
|
||||
<< "</span>\": (" << pango_escape(i.str()) << ") = "
|
||||
<< "<span color=\"orange\">"
|
||||
<< pango_escape(i.value().to_debug_string(NULL, false))
|
||||
<< "</span>" << std::endl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
180
src/tests/test_formula_core.cpp
Normal file
180
src/tests/test_formula_core.cpp
Normal file
|
@ -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 <boost/test/unit_test.hpp>
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#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<variant> 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<int>(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()
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include <cmath>
|
||||
|
||||
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<int>(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<int>(round(1000.0 * log(12))));
|
||||
BOOST_CHECK_EQUAL(formula("exp(3)").evaluate().as_decimal(),
|
||||
static_cast<int>(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<int>(1000. * sin(x * pi / 180.)));
|
||||
, static_cast<int>(round(1000. * sin(x * pi / 180.))));
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
game_logic::formula("cos(x)")
|
||||
.evaluate(variables).as_decimal()
|
||||
, static_cast<int>(1000. * cos(x * pi / 180.)));
|
||||
, static_cast<int>(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<int>(round(1000. * tan(x * pi / 180.))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,17 +19,24 @@
|
|||
#include "formula.hpp"
|
||||
#include "formula_string_utils.hpp"
|
||||
#include "map_location.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
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)
|
||||
|
|
219
src/variant.cpp
219
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<int>(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<variant> 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>& variant::as_list() const
|
||||
{
|
||||
must_be(TYPE_LIST);
|
||||
assert(list_);
|
||||
return list_->elements;
|
||||
}
|
||||
|
||||
const std::map<variant,variant>& 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<int>( res );
|
||||
|
||||
res -= i;
|
||||
|
||||
if( res > 0.5 )
|
||||
i++;
|
||||
|
||||
return variant( i , variant::DECIMAL_VARIANT);
|
||||
return variant(res, DECIMAL_VARIANT);
|
||||
}
|
||||
|
||||
return variant(static_cast<int>(
|
||||
|
@ -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<variant,variant>::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:
|
||||
|
|
|
@ -59,6 +59,7 @@ public:
|
|||
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<variant>* array);
|
||||
explicit variant(const std::string& str);
|
||||
|
@ -68,8 +69,8 @@ public:
|
|||
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 @@ public:
|
|||
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 @@ public:
|
|||
bool as_bool() const;
|
||||
|
||||
bool is_list() const { return type_ == TYPE_LIST; }
|
||||
|
||||
const std::vector<variant>& as_list() const;
|
||||
const std::map<variant,variant>& 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 @@ public:
|
|||
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 @@ private:
|
|||
*/
|
||||
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 @@ public:
|
|||
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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue