Merge pull request #624 from CelticMinstrel/lua_formula_bridge

Many new features in the formula engine
This commit is contained in:
Celtic Minstrel 2016-03-18 12:52:05 -04:00
commit bb510a58af
26 changed files with 1494 additions and 359 deletions

View file

@ -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:

View file

@ -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

View file

@ -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]
)}

View file

@ -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;
};

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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") {

View file

@ -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));
}

View file

@ -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

View file

@ -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;
};

View file

@ -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;

View file

@ -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;
};

View file

@ -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;

View file

@ -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);

View file

@ -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
}

View file

@ -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 );

View file

@ -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, "&lt;");
} else if(str[i-1] == '>') {
str.replace(i-1, 1, "&gt;");
} else if(str[i-1] == '&') {
str.replace(i-1, 1, "&amp;");
} else if(str[i-1] == '"') {
str.replace(i-1, 1, "&quot;");
} else if(str[i-1] == '\'') {
str.replace(i-1, 1, "&apos;");
}
}
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;
}
}

View file

@ -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");
}
}

View file

@ -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);

View 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()

View file

@ -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.))));
}
}
}

View file

@ -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)

View file

@ -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:

View file

@ -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;

View file

@ -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