formula_string_utils.cpp

Go to the documentation of this file.
00001 /* $Id: formula_string_utils.cpp 52533 2012-01-07 02:35:17Z shadowmaster $ */
00002 /*
00003    Copyright (C) 2003 by David White <dave@whitevine.net>
00004    Copyright (C) 2005 - 2012 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
00005    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2 of the License, or
00010    (at your option) any later version.
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY.
00013 
00014    See the COPYING file for more details.
00015 */
00016 
00017 #include "formula_string_utils.hpp"
00018 
00019 #include "config.hpp"
00020 #include "log.hpp"
00021 #include "formula.hpp"
00022 #include "gettext.hpp"
00023 
00024 static lg::log_domain log_engine("engine");
00025 #define ERR_NG LOG_STREAM(err, log_engine)
00026 
00027 static bool two_dots(char a, char b) { return a == '.' && b == '.'; }
00028 
00029 namespace utils {
00030 
00031 class string_map_variable_set : public variable_set
00032 {
00033 public:
00034     string_map_variable_set(const string_map& map) : map_(map) {};
00035 
00036     virtual config::attribute_value get_variable_const(const std::string &key) const
00037     {
00038         config::attribute_value val;
00039         const string_map::const_iterator itor = map_.find(key);
00040         if (itor != map_.end())
00041             val = itor->second;
00042         return val;
00043     }
00044 private:
00045     const string_map& map_;
00046 
00047 };
00048 }
00049 
00050 static std::string do_interpolation(const std::string &str, const variable_set& set)
00051 {
00052     std::string res = str;
00053     // This needs to be able to store negative numbers to check for the while's condition
00054     // (which is only false when the previous '$' was at index 0)
00055     int rfind_dollars_sign_from = res.size();
00056     while(rfind_dollars_sign_from >= 0) {
00057         // Going in a backwards order allows nested variable-retrieval, e.g. in arrays.
00058         // For example, "I am $creatures[$i].user_description!"
00059         const std::string::size_type var_begin_loc = res.rfind('$', rfind_dollars_sign_from);
00060 
00061         // If there are no '$' left then we're done.
00062         if(var_begin_loc == std::string::npos) {
00063             break;
00064         }
00065 
00066         // For the next iteration of the loop, search for more '$'
00067         // (not from the same place because sometimes the '$' is not replaced)
00068         rfind_dollars_sign_from = int(var_begin_loc) - 1;
00069 
00070 
00071         const std::string::iterator var_begin = res.begin() + var_begin_loc;
00072 
00073         // The '$' is not part of the variable name.
00074         const std::string::iterator var_name_begin = var_begin + 1;
00075         std::string::iterator var_end = var_name_begin;
00076 
00077         if(var_name_begin == res.end()) {
00078             // Any '$' at the end of a string is just a '$'
00079             continue;
00080         } else if(*var_name_begin == '(') {
00081             // The $( ... ) syntax invokes a formula
00082             int paren_nesting_level = 0;
00083             bool in_string = false,
00084                 in_comment = false;
00085             do {
00086                 switch(*var_end) {
00087                 case '(':
00088                     if(!in_string && !in_comment) {
00089                         ++paren_nesting_level;
00090                     }
00091                     break;
00092                 case ')':
00093                     if(!in_string && !in_comment) {
00094                         --paren_nesting_level;
00095                     }
00096                     break;
00097                 case '#':
00098                     if(!in_string) {
00099                         in_comment = !in_comment;
00100                     }
00101                     break;
00102                 case '\'':
00103                     if(!in_comment) {
00104                         in_string = !in_string;
00105                     }
00106                     break;
00107                 // TODO: support escape sequences when/if they are allowed in FormulaAI strings
00108                 }
00109             } while(++var_end != res.end() && paren_nesting_level > 0);
00110             if(paren_nesting_level > 0) {
00111                 ERR_NG << "Formula in WML string cannot be evaluated due to "
00112                     << "a missing closing parenthesis:\n\t--> \""
00113                     << std::string(var_begin, var_end) << "\"\n";
00114                 res.replace(var_begin, var_end, "");
00115                 continue;
00116             }
00117             try {
00118                 const game_logic::formula form(std::string(var_begin+2, var_end-1));
00119                 res.replace(var_begin, var_end, form.evaluate().string_cast());
00120             } catch(game_logic::formula_error& e) {
00121                 ERR_NG << "Formula in WML string cannot be evaluated due to "
00122                     << e.type << "\n\t--> \""
00123                     << e.formula << "\"\n";
00124                 res.replace(var_begin, var_end, "");
00125             }
00126             continue;
00127         }
00128 
00129         // Find the maximum extent of the variable name (it may be shortened later).
00130         for(int bracket_nesting_level = 0; var_end != res.end(); ++var_end) {
00131             const char c = *var_end;
00132             if(c == '[') {
00133                 ++bracket_nesting_level;
00134             }
00135             else if(c == ']') {
00136                 if(--bracket_nesting_level < 0) {
00137                     break;
00138                 }
00139             }
00140             // isascii() breaks on mingw with -std=c++0x
00141             else if (!(((c) & ~0x7f) == 0)/*isascii(c)*/ || (!isalnum(c) && c != '.' && c != '_')) {
00142                 break;
00143             }
00144         }
00145 
00146         // Two dots in a row cannot be part of a valid variable name.
00147         // That matters for random=, e.g. $x..$y
00148         var_end = std::adjacent_find(var_name_begin, var_end, two_dots);
00149 
00150         // If the last character is '.', then it can't be a sub-variable.
00151         // It's probably meant to be a period instead. Don't include it.
00152         // Would need to do it repetitively if there are multiple '.'s at the end,
00153         // but don't actually need to do so because the previous check for adjacent '.'s would catch that.
00154         // For example, "My score is $score." or "My score is $score..."
00155         if(*(var_end-1) == '.'
00156         // However, "$array[$i]" by itself does not name a variable,
00157         // so if "$array[$i]." is encountered, then best to include the '.',
00158         // so that it more closely follows the syntax of a variable (if only to get rid of all of it).
00159         // (If it's the script writer's error, they'll have to fix it in either case.)
00160         // For example in "$array[$i].$field_name", if field_name does not exist as a variable,
00161         // then the result of the expansion should be "", not "." (which it would be if this exception did not exist).
00162         && *(var_end-2) != ']') {
00163             --var_end;
00164         }
00165 
00166         const std::string var_name(var_name_begin, var_end);
00167 
00168         if(var_end != res.end() && *var_end == '|') {
00169             // It's been used to end this variable name; now it has no more effect.
00170             // This can allow use of things like "$$composite_var_name|.x"
00171             // (Yes, that's a WML 'pointer' of sorts. They are sometimes useful.)
00172             // If there should still be a '|' there afterwards to affect other variable names (unlikely),
00173             // just put another '|' there, one matching each '$', e.g. "$$var_containing_var_name||blah"
00174             ++var_end;
00175         }
00176 
00177 
00178         if (var_name == "") {
00179             // Allow for a way to have $s in a string.
00180             // $| will be replaced by $.
00181             res.replace(var_begin, var_end, "$");
00182         }
00183         else {
00184             // The variable is replaced with its value.
00185             res.replace(var_begin, var_end,
00186                 set.get_variable_const(var_name));
00187         }
00188     }
00189 
00190     return res;
00191 }
00192 
00193 namespace utils {
00194 
00195 std::string interpolate_variables_into_string(const std::string &str, const string_map * const symbols)
00196 {
00197     string_map_variable_set set(*symbols);
00198     return do_interpolation(str, set);
00199 }
00200 
00201 std::string interpolate_variables_into_string(const std::string &str, const variable_set& variables)
00202 {
00203     return do_interpolation(str, variables);
00204 }
00205 
00206 t_string interpolate_variables_into_tstring(const t_string &tstr, const variable_set& variables)
00207 {
00208     if(!tstr.str().empty()) {
00209         std::string interp = utils::interpolate_variables_into_string(tstr.str(), variables);
00210         if(tstr.str() != interp) {
00211             return t_string(interp);
00212         }
00213     }
00214     return tstr;
00215 }
00216 
00217 }
00218 
00219 std::string vgettext(const char *msgid, const utils::string_map& symbols)
00220 {
00221     const std::string orig(_(msgid));
00222     const std::string msg = utils::interpolate_variables_into_string(orig, &symbols);
00223     return msg;
00224 }
00225 
00226 std::string vgettext(const char *domain
00227         , const char *msgid
00228         , const utils::string_map& symbols)
00229 {
00230     const std::string orig(dgettext(domain, msgid));
00231     const std::string msg = utils::interpolate_variables_into_string(orig, &symbols);
00232     return msg;
00233 }
00234 std::string vngettext(const char* sing, const char* plur, int n, const utils::string_map& symbols)
00235 {
00236     const std::string orig(_n(sing, plur, n));
00237     const std::string msg = utils::interpolate_variables_into_string(orig, &symbols);
00238     return msg;
00239 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:02:51 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs