formula.cpp

Go to the documentation of this file.
00001 /* $Id: formula.cpp 47608 2010-11-21 01:56:29Z shadowmaster $ */
00002 /*
00003    Copyright (C) 2007 by David White <dave.net>
00004    Part of the Silver Tree Project
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published by or later.
00008    This program is distributed in the hope that it will be useful,
00009    but WITHOUT ANY WARRANTY.
00010 
00011    See the COPYING file for more details.
00012 */
00013 #include "global.hpp"
00014 
00015 #include <boost/lexical_cast.hpp>
00016 #include <iostream>
00017 #include <set>
00018 #include <sstream>
00019 
00020 #include "foreach.hpp"
00021 #include "formula_callable.hpp"
00022 #include "formula_function.hpp"
00023 #include "map_utils.hpp"
00024 
00025 namespace game_logic
00026 {
00027 
00028 void formula_callable::set_value(const std::string& key, const variant& /*value*/)
00029 {
00030     std::cerr << "ERROR: cannot set key '" << key << "' on object\n";
00031 }
00032 
00033 
00034 map_formula_callable::map_formula_callable(
00035         const formula_callable* fallback) :
00036     formula_callable(false),
00037     values_(),
00038     fallback_(fallback)
00039 {}
00040 
00041 map_formula_callable& map_formula_callable::add(const std::string& key,
00042                                                 const variant& value)
00043 {
00044     values_[key] = value;
00045     return *this;
00046 }
00047 
00048 variant map_formula_callable::get_value(const std::string& key) const
00049 {
00050     return map_get_value_default(values_, key,
00051             fallback_ ? fallback_->query_value(key) : variant());
00052 }
00053 
00054 void map_formula_callable::get_inputs(std::vector<formula_input>* inputs) const
00055 {
00056     if(fallback_) {
00057         fallback_->get_inputs(inputs);
00058     }
00059     for(std::map<std::string,variant>::const_iterator i = values_.begin(); i != values_.end(); ++i) {
00060         inputs->push_back(formula_input(i->first, FORMULA_READ_WRITE));
00061     }
00062 }
00063 
00064 void map_formula_callable::set_value(const std::string& key, const variant& value)
00065 {
00066     values_[key] = value;
00067 }
00068 
00069 namespace {
00070 
00071 class function_list_expression : public formula_expression {
00072 public:
00073     explicit function_list_expression(function_symbol_table *symbols)
00074         : symbols_(symbols)
00075     {}
00076 
00077     virtual std::string str() const
00078     {
00079         return "{function_list_expression()}";
00080     }
00081 private:
00082     variant execute(const formula_callable& /*variables*/, formula_debugger * /*fdb*/) const {
00083         std::vector<variant> res;
00084         std::vector<std::string> function_names = builtin_function_names();
00085         std::vector<std::string> more_function_names = symbols_->get_function_names();
00086         function_names.insert(function_names.end(), more_function_names.begin(), more_function_names.end());
00087         for(size_t i = 0; i < function_names.size(); i++) {
00088             res.push_back(variant(function_names[i]));
00089         }
00090         return variant(&res);
00091     }
00092 
00093     function_symbol_table* symbols_;
00094 };
00095 
00096 class list_expression : public formula_expression {
00097 public:
00098     explicit list_expression(const std::vector<expression_ptr>& items)
00099        : items_(items)
00100     {}
00101 
00102 private:
00103     variant execute(const formula_callable& variables, formula_debugger *fdb) const {
00104         std::vector<variant> res;
00105         res.reserve(items_.size());
00106         for(std::vector<expression_ptr>::const_iterator i = items_.begin(); i != items_.end(); ++i) {
00107             res.push_back((*i)->evaluate(variables,fdb));
00108         }
00109 
00110         return variant(&res);
00111     }
00112 
00113     std::vector<expression_ptr> items_;
00114 
00115     std::string str() const
00116     {
00117         std::stringstream s;
00118         s << '[';
00119         bool first_item = true;
00120         foreach(expression_ptr a , items_) {
00121             if (!first_item) {
00122                 s << ',';
00123             } else {
00124                 first_item = false;
00125             }
00126             s << a->str();
00127         }
00128         s << ']';
00129         return s.str();
00130     }
00131 
00132 
00133 };
00134 
00135 class map_expression : public formula_expression {
00136 public:
00137     explicit map_expression(const std::vector<expression_ptr>& items)
00138        : items_(items)
00139     {}
00140 
00141     virtual std::string str() const
00142     {
00143         std::stringstream s;
00144         s << " [";
00145         for(std::vector<expression_ptr>::const_iterator i = items_.begin(); ( i != items_.end() ) && ( i+1 != items_.end() ) ; i+=2) {
00146             s << (*i)->str();
00147             s << " -> ";
00148             s << (*(i+1))->str();
00149         }
00150         s << " ]";
00151         return s.str();
00152     }
00153 private:
00154     variant execute(const formula_callable& variables, formula_debugger *fdb) const {
00155         std::map<variant,variant> res;
00156         for(std::vector<expression_ptr>::const_iterator i = items_.begin(); ( i != items_.end() ) && ( i+1 != items_.end() ) ; i+=2) {
00157             variant key = (*i)->evaluate(variables,fdb);
00158             variant value = (*(i+1))->evaluate(variables,fdb);
00159             res[ key ] = value;
00160         }
00161 
00162         return variant(&res);
00163     }
00164 
00165     std::vector<expression_ptr> items_;
00166 };
00167 
00168 class unary_operator_expression : public formula_expression {
00169 public:
00170     unary_operator_expression(const std::string& op, expression_ptr arg) :
00171         op_(),op_str_(op),
00172         operand_(arg)
00173     {
00174         if(op == "not") {
00175             op_ = NOT;
00176         } else if(op == "-") {
00177             op_ = SUB;
00178         } else {
00179             throw formula_error("Illegal unary operator: '" + op + "'" , "", "", 0);
00180         }
00181     }
00182 
00183     virtual std::string str() const {
00184         std::stringstream s;
00185         s << op_str_ << '('<< operand_->str() << ')';
00186         return s.str();
00187     }
00188 
00189 private:
00190     variant execute(const formula_callable& variables, formula_debugger *fdb) const {
00191         const variant res = operand_->evaluate(variables,fdb);
00192         switch(op_) {
00193         case NOT:
00194             return res.as_bool() ? variant(0) : variant(1);
00195         case SUB:
00196         default:
00197             return -res;
00198         }
00199     }
00200     enum OP { NOT, SUB };
00201     OP op_;
00202     std::string op_str_;
00203     expression_ptr operand_;
00204 };
00205 
00206 class list_callable : public formula_callable {
00207     variant list_;
00208 public:
00209     explicit list_callable(const variant& list) : list_(list)
00210     {}
00211 
00212     void get_inputs(std::vector<formula_input>* inputs) const {
00213         inputs->push_back(formula_input("size", FORMULA_READ_WRITE));
00214         inputs->push_back(formula_input("empty", FORMULA_READ_WRITE));
00215         inputs->push_back(formula_input("first", FORMULA_READ_WRITE));
00216         inputs->push_back(formula_input("last", FORMULA_READ_WRITE));
00217     }
00218 
00219     variant get_value(const std::string& key) const {
00220         if(key == "size") {
00221             return variant(list_.num_elements());
00222         } else if(key == "empty") {
00223             return variant(list_.num_elements() == 0);
00224         } else if(key == "first") {
00225             if(list_.num_elements() > 0) {
00226                 return list_[0];
00227             } else {
00228                 return variant();
00229             }
00230         } else if(key == "last") {
00231             if(list_.num_elements() > 0) {
00232                 return list_[list_.num_elements()-1];
00233             } else {
00234                 return variant();
00235             }
00236         } else {
00237             return variant();
00238         }
00239     }
00240 
00241 };
00242 
00243 class dot_callable : public formula_callable {
00244 public:
00245     dot_callable(const formula_callable &global,
00246             const formula_callable& local)
00247         : global_(global), local_(local) { }
00248 private:
00249     const formula_callable& global_, &local_;
00250 
00251     void get_inputs(std::vector<formula_input>* inputs) const {
00252             return local_.get_inputs(inputs);
00253     }
00254 
00255     variant get_value(const std::string& key) const {
00256             variant v = local_.query_value(key);
00257 
00258             if ( v == variant() )
00259         return global_.query_value(key);
00260             else
00261                 return v;
00262     }
00263 };
00264 
00265 class dot_expression : public formula_expression {
00266 public:
00267     dot_expression(expression_ptr left, expression_ptr right)
00268        : left_(left), right_(right)
00269     {}
00270     std::string str() const
00271     {
00272         std::stringstream s;
00273         s << left_->str() << "." << right_->str();
00274         return s.str();
00275     }
00276 private:
00277     variant execute(const formula_callable& variables, formula_debugger *fdb) const {
00278         const variant left = left_->evaluate(variables,add_debug_info(fdb,0,"left."));
00279         if(!left.is_callable()) {
00280             if(left.is_list()) {
00281                                 list_callable list_call(left);
00282                                 dot_callable callable(variables, list_call);
00283                 return right_->evaluate(callable,fdb);
00284             }
00285 
00286             return left;
00287         }
00288 
00289                 dot_callable callable(variables, *left.as_callable());
00290                 return right_->evaluate(callable,add_debug_info(fdb,1,".right"));
00291         }
00292 
00293     expression_ptr left_, right_;
00294 };
00295 
00296 class square_bracket_expression : public formula_expression {
00297 public:
00298     square_bracket_expression(expression_ptr left, expression_ptr key)
00299        : left_(left), key_(key)
00300     {}
00301 
00302     std::string str() const
00303     {
00304         std::stringstream s;
00305         s << left_->str() << '[' << key_->str() << ']';
00306         return s.str();
00307     }
00308 private:
00309     variant execute(const formula_callable& variables, formula_debugger *fdb) const {
00310         const variant left = left_->evaluate(variables,fdb);
00311         const variant key = key_->evaluate(variables,fdb);
00312         if(left.is_list() || left.is_map()) {
00313             return left[ key ];
00314         } else {
00315             return variant();
00316         }
00317     }
00318 
00319     expression_ptr left_, key_;
00320 };
00321 
00322 class operator_expression : public formula_expression {
00323 public:
00324     operator_expression(const std::string& op, expression_ptr left,
00325                                  expression_ptr right)
00326         : op_(OP(op[0])), op_str_(op), left_(left), right_(right)
00327     {
00328         if(op == ">=") {
00329             op_ = GTE;
00330         } else if(op == "<=") {
00331             op_ = LTE;
00332         } else if(op == "!=") {
00333             op_ = NEQ;
00334         } else if(op == "and") {
00335             op_ = AND;
00336         } else if(op == "or") {
00337             op_ = OR;
00338         } else if(op == ".+") {
00339             op_ = ADDL;
00340         } else if(op == ".-") {
00341             op_ = SUBL;
00342         } else if(op == ".*") {
00343             op_ = MULL;
00344         } else if(op == "./") {
00345             op_ = DIVL;
00346         }
00347     }
00348 
00349     std::string str() const
00350     {
00351         std::stringstream s;
00352         s << left_->str() << op_str_ << right_->str();
00353         return s.str();
00354     }
00355 private:
00356     variant execute(const formula_callable& variables, formula_debugger *fdb) const {
00357         const variant left = left_->evaluate(variables,add_debug_info(fdb,0,"left_OP"));
00358         const variant right = right_->evaluate(variables,add_debug_info(fdb,1,"OP_right"));
00359         switch(op_) {
00360         case AND:
00361             return left.as_bool() == false ? left : right;
00362         case OR:
00363             return left.as_bool() ? left : right;
00364         case ADD:
00365             return left + right;
00366         case SUB:
00367             return left - right;
00368         case MUL:
00369             return left * right;
00370         case DIV:
00371             return left / right;
00372         case POW:
00373             return left ^ right;
00374         case ADDL:
00375             return left.list_elements_add(right);
00376         case SUBL:
00377             return left.list_elements_sub(right);
00378         case MULL:
00379             return left.list_elements_mul(right);
00380         case DIVL:
00381             return left.list_elements_div(right);
00382         case EQ:
00383             return left == right ? variant(1) : variant(0);
00384         case NEQ:
00385             return left != right ? variant(1) : variant(0);
00386         case LTE:
00387             return left <= right ? variant(1) : variant(0);
00388         case GTE:
00389             return left >= right ? variant(1) : variant(0);
00390         case LT:
00391             return left < right ? variant(1) : variant(0);
00392         case GT:
00393             return left > right ? variant(1) : variant(0);
00394         case MOD:
00395             return left % right;
00396         case DICE:
00397         default:
00398             return variant(dice_roll(left.as_int(), right.as_int()));
00399         }
00400     }
00401 
00402     static int dice_roll(int num_rolls, int faces) {
00403         int res = 0;
00404         while(faces > 0 && num_rolls-- > 0) {
00405             res += (rand()%faces)+1;
00406         }
00407         return res;
00408     }
00409 
00410     enum OP { AND, OR, NEQ, LTE, GTE, GT='>', LT='<', EQ='=',
00411               ADD='+', SUB='-', MUL='*', DIV='/', ADDL, SUBL, MULL, DIVL, DICE='d', POW='^', MOD='%' };
00412 
00413     OP op_;
00414     std::string op_str_;
00415     expression_ptr left_, right_;
00416 };
00417 
00418 typedef std::map<std::string,expression_ptr> expr_table;
00419 typedef boost::shared_ptr<expr_table> expr_table_ptr;
00420 typedef std::map<std::string, variant> exp_table_evaluated;
00421 
00422 class where_variables: public formula_callable {
00423 public:
00424     where_variables(const formula_callable &base,
00425             expr_table_ptr table )
00426         : formula_callable(false)
00427         , base_(base)
00428         , table_(table)
00429         , evaluated_table_()
00430     {
00431     }
00432 
00433 private:
00434     const formula_callable& base_;
00435     expr_table_ptr table_;
00436     mutable exp_table_evaluated evaluated_table_;
00437 
00438     void get_inputs(std::vector<formula_input>* inputs) const {
00439         for(expr_table::const_iterator i = table_->begin(); i != table_->end(); ++i) {
00440             inputs->push_back(formula_input(i->first, FORMULA_READ_ONLY));
00441         }
00442     }
00443 
00444     variant get_value(const std::string& key) const {
00445         expr_table::iterator i = table_->find(key);
00446         if(i != table_->end()) {
00447             exp_table_evaluated::const_iterator ev = evaluated_table_.find(key);
00448             if( ev != evaluated_table_.end())
00449                 return ev->second;
00450 
00451             variant v = i->second->evaluate(base_);
00452             evaluated_table_[key] = v;
00453             return v;
00454         }
00455         return base_.query_value(key);
00456     }
00457 };
00458 
00459 class where_expression: public formula_expression {
00460 public:
00461     explicit where_expression(expression_ptr body,
00462                   expr_table_ptr clauses)
00463         : body_(body), clauses_(clauses)
00464     {}
00465 
00466     std::string str() const
00467     {
00468         std::stringstream s;
00469         s << "{where:(";
00470         s << body_->str();
00471         foreach (const expr_table::value_type &a, *clauses_) {
00472             s << ", [" << a.first << "] -> ["<< a.second->str()<<"]";
00473         }
00474         s << ")}";
00475         return s.str();
00476     }
00477 
00478 private:
00479     expression_ptr body_;
00480     expr_table_ptr clauses_;
00481 
00482     variant execute(const formula_callable& variables,formula_debugger *fdb) const {
00483         where_variables wrapped_variables(variables, clauses_);
00484         return body_->evaluate(wrapped_variables,fdb);
00485     }
00486 };
00487 
00488 
00489 class identifier_expression : public formula_expression {
00490 public:
00491     explicit identifier_expression(const std::string& id) : id_(id)
00492     {}
00493     std::string str() const
00494     {
00495         return id_;
00496     }
00497 private:
00498     variant execute(const formula_callable& variables, formula_debugger * /*fdb*/) const {
00499         return variables.query_value(id_);
00500     }
00501     std::string id_;
00502 };
00503 
00504 class null_expression : public formula_expression {
00505 public:
00506     explicit null_expression() {};
00507     std::string str() const {
00508         return "";
00509     }
00510 private:
00511     variant execute(const formula_callable& /*variables*/, formula_debugger * /*fdb*/) const {
00512         return variant();
00513     }
00514 };
00515 
00516 
00517 class integer_expression : public formula_expression {
00518 public:
00519     explicit integer_expression(int i) : i_(i)
00520     {}
00521     std::string str() const
00522     {
00523         std::stringstream s;
00524         s << i_;
00525         return s.str();
00526     }
00527 private:
00528     variant execute(const formula_callable& /*variables*/, formula_debugger * /*fdb*/) const {
00529         return variant(i_);
00530     }
00531 
00532     int i_;
00533 };
00534 
00535 class decimal_expression : public formula_expression {
00536 public:
00537     explicit decimal_expression(int i, int f) : i_(i), f_(f)
00538     {}
00539 
00540     std::string str() const
00541     {
00542         std::stringstream s;
00543         s << i_ << '.' << f_;
00544         return s.str();
00545     }
00546 private:
00547     variant execute(const formula_callable& /*variables*/, formula_debugger * /*fdb*/) const {
00548         return variant(i_ * 1000 + f_, variant::DECIMAL_VARIANT );
00549     }
00550 
00551     int i_, f_;
00552 };
00553 
00554 class string_expression : public formula_expression {
00555 public:
00556     explicit string_expression(std::string str) :
00557         str_(),
00558         subs_()
00559     {
00560         std::string::iterator i;
00561         while((i = std::find(str.begin(), str.end(), '{')) != str.end()) {
00562             std::string::iterator j = std::find(i, str.end(), '}');
00563             if(j == str.end()) {
00564                 break;
00565             }
00566 
00567             const std::string formula_str(i+1, j);
00568             const int pos = i - str.begin();
00569             str.erase(i, j+1);
00570 
00571             substitution sub;
00572             sub.pos = pos;
00573             sub.calculation.reset(new formula(formula_str));
00574             subs_.push_back(sub);
00575         }
00576 
00577         std::reverse(subs_.begin(), subs_.end());
00578 
00579         str_ = variant(str);
00580     }
00581 
00582     std::string str() const
00583     {
00584         return str_.as_string();
00585     }
00586 private:
00587     variant execute(const formula_callable& variables, formula_debugger *fdb) const {
00588         if(subs_.empty()) {
00589             return str_;
00590         } else {
00591             std::string res = str_.as_string();
00592             for(size_t i=0; i < subs_.size(); ++i) {
00593                 const substitution& sub = subs_[i];
00594                 const std::string str = sub.calculation->evaluate(variables,fdb).string_cast();
00595                 res.insert(sub.pos, str);
00596             }
00597 
00598             return variant(res);
00599         }
00600     }
00601 
00602     struct substitution {
00603 
00604         substitution() :
00605             pos(0),
00606             calculation()
00607         {
00608         }
00609 
00610         int pos;
00611         const_formula_ptr calculation;
00612     };
00613 
00614     variant str_;
00615     std::vector<substitution> subs_;
00616 };
00617 
00618 using namespace formula_tokenizer;
00619 int operator_precedence(const token& t)
00620 {
00621     static std::map<std::string,int> precedence_map;
00622     if(precedence_map.empty()) {
00623         int n = 0;
00624         precedence_map["not"] = ++n;
00625         precedence_map["where"] = ++n;
00626         precedence_map["or"]    = ++n;
00627         precedence_map["and"]   = ++n;
00628         precedence_map["="]     = ++n;
00629         precedence_map["!="]    = n;
00630         precedence_map["<"]     = n;
00631         precedence_map[">"]     = n;
00632         precedence_map["<="]    = n;
00633         precedence_map[">="]    = n;
00634         precedence_map["+"]     = ++n;
00635         precedence_map["-"]     = n;
00636         precedence_map["*"]     = ++n;
00637         precedence_map["/"]     = ++n;
00638         precedence_map["%"]     = ++n;
00639         precedence_map["^"]     = ++n;
00640         precedence_map["d"]     = ++n;
00641         precedence_map["."]     = ++n;
00642     }
00643 
00644     assert(precedence_map.count(std::string(t.begin,t.end)));
00645     return precedence_map[std::string(t.begin,t.end)];
00646 }
00647 
00648 expression_ptr parse_expression(const token* i1, const token* i2, function_symbol_table* symbols);
00649 
00650 
00651 //function used when creating error reports, parses all tokens passed to parse_expression, thus there are no EOL or whitespaces
00652 std::string tokens_to_string(const token* i1, const token* i2)
00653 {
00654         std::ostringstream expr;
00655         while(i1 != i2) {
00656             expr << std::string(i1->begin,i1->end) << " ";
00657             ++i1;
00658         }
00659         return expr.str();
00660 }
00661 
00662 void parse_function_args(const token* &i1, const token* i2,
00663         std::vector<std::string>* res)
00664 {
00665     const token* begin = i1, *end = i2; //these are used for error reporting
00666 
00667     if(i1->type == TOKEN_LPARENS) {
00668         ++i1;
00669     } else {
00670         throw formula_error("Invalid function definition", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00671     }
00672 
00673     while((i1-> type != TOKEN_RPARENS) && (i1 != i2)) {
00674         if(i1->type == TOKEN_IDENTIFIER) {
00675             if(std::string((i1+1)->begin, (i1+1)->end) == "*") {
00676                 res->push_back(std::string(i1->begin, i1->end) + std::string("*"));
00677                 ++i1;
00678             } else {
00679                 res->push_back(std::string(i1->begin, i1->end));
00680             }
00681         } else if (i1->type == TOKEN_COMMA) {
00682             //do nothing
00683         } else {
00684             throw formula_error("Invalid function definition", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00685         }
00686         ++i1;
00687     }
00688 
00689     if(i1->type != TOKEN_RPARENS) {
00690         throw formula_error("Invalid function definition", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00691     }
00692     ++i1;
00693 }
00694 
00695 void parse_args(const token* i1, const token* i2,
00696                 std::vector<expression_ptr>* res,
00697                 function_symbol_table* symbols)
00698 {
00699     int parens = 0;
00700     const token* beg = i1;
00701     while(i1 != i2) {
00702         if(i1->type == TOKEN_LPARENS || i1->type == TOKEN_LSQUARE ) {
00703             ++parens;
00704         } else if(i1->type == TOKEN_RPARENS || i1->type == TOKEN_RSQUARE ) {
00705             --parens;
00706         } else if(i1->type == TOKEN_COMMA && !parens) {
00707             res->push_back(parse_expression(beg,i1, symbols));
00708             beg = i1+1;
00709         }
00710         ++i1;
00711     }
00712 
00713     if(beg != i1) {
00714         res->push_back(parse_expression(beg,i1, symbols));
00715     }
00716 }
00717 
00718 void parse_set_args(const token* i1, const token* i2,
00719                 std::vector<expression_ptr>* res,
00720                 function_symbol_table* symbols)
00721 {
00722     int parens = 0;
00723     bool check_pointer = false;
00724     const token* beg = i1;
00725     const token* begin = i1, *end = i2; //these are used for error reporting
00726     while(i1 != i2) {
00727         if(i1->type == TOKEN_LPARENS || i1->type == TOKEN_LSQUARE) {
00728             ++parens;
00729         } else if(i1->type == TOKEN_RPARENS || i1->type == TOKEN_RSQUARE) {
00730             --parens;
00731         } else if( i1->type == TOKEN_POINTER && !parens ) {
00732             if (!check_pointer) {
00733                 check_pointer = true;
00734                 res->push_back(parse_expression(beg,i1, symbols));
00735                 beg = i1+1;
00736             } else {
00737                 throw formula_error("Too many '->' operators found", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00738             }
00739         } else if( i1->type == TOKEN_COMMA && !parens ) {
00740             if (check_pointer)
00741                 check_pointer = false;
00742             else {
00743                 throw formula_error("Expected comma, but '->' found", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00744         }
00745             res->push_back(parse_expression(beg,i1, symbols));
00746             beg = i1+1;
00747         }
00748 
00749         ++i1;
00750     }
00751 
00752     if(beg != i1) {
00753         res->push_back(parse_expression(beg,i1, symbols));
00754     }
00755 }
00756 
00757 void parse_where_clauses(const token* i1, const token * i2,
00758                          expr_table_ptr res, function_symbol_table* symbols) {
00759     int parens = 0;
00760     const token *original_i1_cached = i1;
00761     const token *beg = i1;
00762     const token* begin = i1, *end = i2; //these are used for error reporting
00763     std::string var_name;
00764     while(i1 != i2) {
00765         if(i1->type == TOKEN_LPARENS) {
00766             ++parens;
00767         } else if(i1->type == TOKEN_RPARENS) {
00768             --parens;
00769         } else if(!parens) {
00770             if(i1->type == TOKEN_COMMA) {
00771                 if(var_name.empty()) {
00772                     throw formula_error("There is 'where <expression>' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00773                 }
00774                 (*res)[var_name] = parse_expression(beg,i1, symbols);
00775                 beg = i1+1;
00776                 var_name = "";
00777             } else if(i1->type == TOKEN_OPERATOR) {
00778                 std::string op_name(i1->begin, i1->end);
00779                 if(op_name == "=") {
00780                     if(beg->type != TOKEN_IDENTIFIER) {
00781                         if(i1 == original_i1_cached) {
00782                             throw formula_error("There is 'where <expression' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00783                         } else {
00784                             throw formula_error("There is 'where <expression>=<expression>' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00785                         }
00786                     } else if(beg+1 != i1) {
00787                         throw formula_error("There is 'where name <expression>=<expression>' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00788                     } else if(!var_name.empty()) {
00789                         throw formula_error("There is 'where name=name=<expression>' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00790                     }
00791                     var_name.insert(var_name.end(), beg->begin, beg->end);
00792                     beg = i1+1;
00793                 }
00794             }
00795         }
00796         ++i1;
00797     }
00798     if(beg != i1) {
00799         if(var_name.empty()) {
00800             throw formula_error("There is 'where <expression>' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
00801         }
00802         (*res)[var_name] = parse_expression(beg,i1, symbols);
00803     }
00804 }
00805 
00806 expression_ptr parse_expression(const token* i1, const token* i2, function_symbol_table* symbols)
00807 {
00808     if(i1 == i2) {
00809         throw formula_error("Empty expression", "", *i1->filename, i1->line_number);
00810     }
00811 
00812     const token* begin = i1, *end = i2; //these are used for error reporting
00813 
00814     if(i1->type == TOKEN_KEYWORD &&
00815             (i1+1)->type == TOKEN_IDENTIFIER) {
00816         if(std::string(i1->begin, i1->end) == "def") {
00817             ++i1;
00818             const std::string formula_name = std::string(i1->begin, i1->end);
00819             std::vector<std::string> args;
00820             parse_function_args(++i1, i2, &args);
00821             const token* beg = i1;
00822             while((i1 != i2) && (i1->type != TOKEN_SEMICOLON)) {
00823                 ++i1;
00824             }
00825             const std::string precond = "";
00826             if(symbols == NULL) {
00827                 throw formula_error("Function symbol table required but not present", "",*i1->filename, i1->line_number);
00828             }
00829             symbols->add_formula_function(formula_name,
00830                     const_formula_ptr(new formula(beg, i1, symbols)),
00831                     formula::create_optional_formula(precond, symbols),
00832                     args);
00833             if((i1 == i2) || (i1 == (i2-1))) {
00834                 return expression_ptr(new function_list_expression(symbols));
00835             }
00836             else {
00837                 return parse_expression((i1+1), i2, symbols);
00838             }
00839         }
00840     }
00841 
00842     int parens = 0;
00843     const token* op = NULL;
00844     bool operator_group = false;
00845     for(const token* i = i1; i != i2; ++i) {
00846         if(i->type == TOKEN_LPARENS || i->type == TOKEN_LSQUARE) {
00847             ++parens;
00848         } else if(i->type == TOKEN_RPARENS || i->type == TOKEN_RSQUARE) {
00849             --parens;
00850         } else if(parens == 0 && i->type == TOKEN_OPERATOR) {
00851             if( ( !operator_group ) && (op == NULL || operator_precedence(*op) >=
00852                              operator_precedence(*i)) ) {
00853                 op = i;
00854             }
00855                         operator_group = true;
00856         } else {
00857             operator_group = false;
00858         }
00859     }
00860 
00861     if(op == NULL) {
00862         if(i1->type == TOKEN_LPARENS && (i2-1)->type == TOKEN_RPARENS) {
00863             return parse_expression(i1+1,i2-1,symbols);
00864         } else if( (i2-1)->type == TOKEN_RSQUARE) { //check if there is [ ] : either a list/map definition, or a operator
00865                 const token* tok = i2-2;
00866                 int square_parens = 0;
00867                                 bool is_map = false;
00868                 while ( (tok->type != TOKEN_LSQUARE || square_parens) && tok != i1) {
00869                         if (tok->type == TOKEN_RSQUARE) {
00870                             square_parens++;
00871                         } else if(tok->type == TOKEN_LSQUARE) {
00872                             square_parens--;
00873                         } else if( (tok->type == TOKEN_POINTER) && !square_parens ) {
00874                                                     is_map = true;
00875                                                 }
00876                         --tok;
00877                 }
00878                 if (tok->type == TOKEN_LSQUARE) {
00879                     if (tok == i1) {
00880                         //create a list or a map
00881                         std::vector<expression_ptr> args;
00882 
00883                         if ( is_map ) {
00884                             parse_set_args(i1+1, i2-1, &args, symbols);
00885                             return expression_ptr(new map_expression(args));
00886                         } else {
00887                             parse_args(i1+1,i2-1,&args,symbols);
00888                             return expression_ptr(new list_expression(args));
00889                         }
00890                     } else {
00891                         //execute operator [ ]
00892                         try{
00893                             return expression_ptr(new square_bracket_expression(
00894                                             parse_expression(i1,tok,symbols),
00895                                                 parse_expression(tok+1,i2-1,symbols)));
00896                         }
00897                         catch (formula_error& e){
00898                             throw formula_error( e.type, tokens_to_string(i1, i2-1), *i1->filename, i1->line_number );
00899                         }
00900                     }
00901                 }
00902         } else if(i2 - i1 == 1) {
00903             if(i1->type == TOKEN_KEYWORD) {
00904                 if(std::string(i1->begin,i1->end) == "functions") {
00905                     return expression_ptr(new function_list_expression(symbols));
00906                 }
00907             } else if(i1->type == TOKEN_IDENTIFIER) {
00908                 return expression_ptr(new identifier_expression(
00909                                  std::string(i1->begin,i1->end)));
00910             } else if(i1->type == TOKEN_INTEGER) {
00911                 int n = atoi(std::string(i1->begin,i1->end).c_str());
00912                 return expression_ptr(new integer_expression(n));
00913             } else if(i1->type == TOKEN_DECIMAL) {
00914                 iterator dot = i1->begin;
00915                 while( *dot != '.' )
00916                     ++dot;
00917 
00918                 int n = atoi(std::string(i1->begin,dot).c_str());
00919 
00920                 iterator end = i1->end;
00921 
00922                 if( end - dot > 4)
00923                     end = dot + 4;
00924 
00925                 ++dot;
00926 
00927                 int f = 0;
00928 
00929                 int multiplicator = 100;
00930                 while( dot != end) {
00931                     f += (*dot - 48)*multiplicator;
00932                     multiplicator /= 10;
00933                     ++dot;
00934                 }
00935 
00936                 return expression_ptr(new decimal_expression(n, f));
00937             } else if(i1->type == TOKEN_STRING_LITERAL) {
00938                 return expression_ptr(new string_expression(std::string(i1->begin+1,i1->end-1)));
00939             }
00940         } else if(i1->type == TOKEN_IDENTIFIER &&
00941                   (i1+1)->type == TOKEN_LPARENS &&
00942                   (i2-1)->type == TOKEN_RPARENS) {
00943             const token* begin = i1, *end = i2; //these are used for error reporting
00944             int nleft = 0, nright = 0;
00945             for(const token* i = i1; i != i2; ++i) {
00946                 if(i->type == TOKEN_LPARENS) {
00947                     ++nleft;
00948                 } else if(i->type == TOKEN_RPARENS) {
00949                     ++nright;
00950                 }
00951             }
00952 
00953             if(nleft == nright) {
00954                 std::vector<expression_ptr> args;
00955                 parse_args(i1+2,i2-1,&args,symbols);
00956                 try{
00957                     return expression_ptr( create_function(std::string(i1->begin,i1->end),args,symbols) );
00958                 }
00959                 catch(formula_error& e) {
00960                     throw formula_error(e.type, tokens_to_string(begin,end), *i1->filename, i1->line_number);
00961                 }
00962             }
00963         }
00964 
00965         throw formula_error("Could not parse expression", tokens_to_string(i1, i2), *i1->filename, i1->line_number);
00966     }
00967     if (op + 1 == end) {
00968         throw formula_error("Expected another token", tokens_to_string(begin,end-1), *op->filename, op->line_number);
00969     }
00970     if(op == i1) {
00971         try{
00972             return expression_ptr(new unary_operator_expression(
00973                                  std::string(op->begin,op->end),
00974                                  parse_expression(op+1,i2,symbols)));
00975         }
00976         catch(formula_error& e) {
00977             throw formula_error( e.type, tokens_to_string(begin,end-1), *op->filename, op->line_number);
00978         }
00979     }
00980 
00981     const std::string op_name(op->begin,op->end);
00982 
00983     if(op_name == ".") {
00984         return expression_ptr(new dot_expression(
00985                                  parse_expression(i1,op,symbols),
00986                                  parse_expression(op+1,i2,symbols)));
00987     }
00988 
00989     if(op_name == "where") {
00990         expr_table_ptr table(new expr_table());
00991         parse_where_clauses(op+1, i2, table, symbols);
00992         return expression_ptr(new where_expression(parse_expression(i1, op, symbols),
00993                                table));
00994     }
00995 
00996     return expression_ptr(new operator_expression(
00997                                  op_name, parse_expression(i1,op,symbols),
00998                                  parse_expression(op+1,i2,symbols)));
00999 }
01000 
01001 }
01002 
01003 formula_ptr formula::create_optional_formula(const std::string& str, function_symbol_table* symbols)
01004 {
01005     if(str.empty()) {
01006         return formula_ptr();
01007     }
01008 
01009     return formula_ptr(new formula(str, symbols));
01010 }
01011 
01012 formula::formula(const std::string& str, function_symbol_table* symbols) :
01013     expr_(),
01014     str_(str)
01015 {
01016     using namespace formula_tokenizer;
01017 
01018     std::vector<token> tokens;
01019     std::string::const_iterator i1 = str.begin(), i2 = str.end();
01020 
01021     //set true when 'fai' keyword is found
01022     bool fai_keyword = false;
01023     //used to locally keep the track of which file we parse actually and in which line we are
01024     std::vector< std::pair< std::string, int> > files;
01025     //used as a source of strings - we point to these strings from tokens
01026     std::set< std::string > filenames;
01027     files.push_back( std::make_pair( "formula", 1 ) );
01028     filenames.insert( "formula" );
01029     std::set< std::string >::iterator filenames_it = filenames.begin();
01030 
01031     while(i1 != i2) {
01032         try {
01033 
01034             tokens.push_back( get_token(i1,i2) );
01035 
01036             TOKEN_TYPE current_type = tokens.back().type;
01037 
01038             if(current_type == TOKEN_WHITESPACE)  {
01039                 tokens.pop_back();
01040             } else
01041             if( current_type == TOKEN_COMMENT) {
01042                 //since we can have multiline comments, let's see how many EOL are within it
01043                 int counter = 0;
01044                 std::string comment = std::string(tokens.back().begin,tokens.back().end);
01045                 for( std::string::iterator str_it = comment.begin(); str_it != comment.end(); ++str_it)
01046                     if( *str_it == '\n' )
01047                         counter++;
01048 
01049                 files.back().second+=counter;
01050                 tokens.pop_back();
01051             } else
01052             if( current_type == TOKEN_EOL) {
01053                 files.back().second++;
01054                 tokens.pop_back();
01055             } else
01056             if( ( current_type == TOKEN_KEYWORD) && ( std::string(tokens.back().begin,tokens.back().end)  == "fai") ) {
01057                 fai_keyword = true;
01058                 tokens.pop_back();
01059             } else
01060             if( ( current_type == TOKEN_KEYWORD) && ( std::string(tokens.back().begin,tokens.back().end) == "faiend") ) {
01061                 if (files.size() > 1) {
01062                     files.pop_back();
01063                     filenames_it = filenames.find( files.back().first );
01064                     tokens.pop_back();
01065                 } else {
01066                     throw formula_error("Unexpected 'faiend' found", "", "", 0);
01067                 }
01068             } else
01069             if (fai_keyword) {
01070                 if(current_type == TOKEN_STRING_LITERAL) {
01071                     std::string str = std::string(tokens.back().begin,tokens.back().end);
01072                     files.push_back( std::make_pair( str , 1 ) );
01073                     std::pair < std::set< std::string>::iterator , bool > ret;
01074                     ret = filenames.insert( str );
01075                     if(ret.second==true) {
01076                         filenames_it = ret.first;
01077                     } else {
01078                         throw formula_error("Faifile already included", "fai" + str, "", 0);
01079                     }
01080                     tokens.pop_back();
01081                     fai_keyword = false;
01082                 } else {
01083                     throw formula_error("Expected string after the 'fai'", "fai", "", 0);
01084                 }
01085             } else {
01086                 //in every token not specified above, store line number and name of file it came from
01087                 tokens.back().filename = &(*filenames_it);
01088                 tokens.back().line_number = files.back().second;
01089             }
01090         } catch(token_error& e) {
01091             //when we catch token error, we should write whole line in which error occurred, so we merge info from token and everything we had in the line so far
01092             std::string str = "";
01093             if(!tokens.empty()) {
01094                 token* tok_it = &tokens[0] + tokens.size()-1;
01095                 while( ( tok_it != &tokens[0] ) && (tok_it->line_number == tokens.back().line_number) )
01096                     --tok_it;
01097 
01098                 if( tok_it != &tokens[0] && tok_it != &tokens[0] + tokens.size() -1)
01099                     ++tok_it;
01100 
01101                 str = tokens_to_string( tok_it, &tokens[0] + tokens.size() );
01102             }
01103 
01104             throw formula_error(e.description_, str + e.formula_, *filenames_it, files.back().second);
01105         }
01106     }
01107 
01108     if(files.size() > 1) {
01109         throw formula_error("Missing 'faiend', make sure each .fai file ends with it", "", "", 0);
01110     }
01111 
01112     if(!tokens.empty()) {
01113         expr_ = parse_expression(&tokens[0],&tokens[0] + tokens.size(), symbols);
01114     } else {
01115         expr_ = expression_ptr(new null_expression());
01116     }
01117 }
01118 formula::formula(const token* i1, const token* i2, function_symbol_table* symbols) :
01119     expr_(),
01120     str_()
01121 {
01122 
01123     if(i1 != i2) {
01124         expr_ = parse_expression(i1,i2, symbols);
01125     } else {
01126         expr_ = expression_ptr(new null_expression());
01127     }
01128 }
01129 
01130 variant formula::execute(const formula_callable& variables, formula_debugger *fdb) const
01131 {
01132     try {
01133         return expr_->evaluate(variables, fdb);
01134     } catch(type_error& e) {
01135         std::cerr << "formula type error: " << e.message << "\n";
01136         return variant();
01137     }
01138 }
01139 
01140 variant formula::execute(formula_debugger *fdb) const
01141 {
01142     static map_formula_callable null_callable;
01143     return execute(null_callable,fdb);
01144 }
01145 
01146 formula_error::formula_error(const std::string& type, const std::string& formula,
01147         const std::string& file, int line)
01148     : error()
01149     , type(type)
01150     , formula(formula)
01151     , filename(file)
01152     , line(line)
01153 {
01154     std::stringstream ss;
01155     ss << "Formula error in " << filename << ":" << line
01156        << "\nIn formula " << formula
01157        << "\nError: " << type;
01158     message = ss.str();
01159 }
01160 
01161 }
01162 
01163 #ifdef UNIT_TEST_FORMULA
01164 using namespace game_logic;
01165 class mock_char : public formula_callable {
01166     variant get_value(const std::string& key) const {
01167         if(key == "strength") {
01168             return variant(15);
01169         } else if(key == "agility") {
01170             return variant(12);
01171         }
01172 
01173         return variant(10);
01174     }
01175 };
01176 class mock_party : public formula_callable {
01177     variant get_value(const std::string& key) const {
01178         if(key == "members") {
01179             i_[0].add("strength",variant(12));
01180             i_[1].add("strength",variant(16));
01181             i_[2].add("strength",variant(14));
01182             std::vector<variant> members;
01183             for(int n = 0; n != 3; ++n) {
01184                 members.push_back(variant(&i_[n]));
01185             }
01186 
01187             return variant(&members);
01188         } else if(key == "char") {
01189             return variant(&c_);
01190         } else {
01191             return variant(0);
01192         }
01193     }
01194 
01195     mock_char c_;
01196     mutable map_formula_callable i_[3];
01197 
01198 };
01199 
01200 #include <time.h>
01201 
01202 int main()
01203 {
01204     srand(time(NULL));
01205     try {
01206         mock_char c;
01207         mock_party p;
01208 
01209         assert(formula("strength").execute(c).as_int() == 15);
01210         assert(formula("17").execute(c).as_int() == 17);
01211         assert(formula("strength/2 + agility").execute(c).as_int() == 19);
01212         assert(formula("(strength+agility)/2").execute(c).as_int() == 13);
01213         assert(formula("strength > 12").execute(c).as_int() == 1);
01214         assert(formula("strength > 18").execute(c).as_int() == 0);
01215         assert(formula("if(strength > 12, 7, 2)").execute(c).as_int() == 7);
01216         assert(formula("if(strength > 18, 7, 2)").execute(c).as_int() == 2);
01217         assert(formula("2 and 1").execute(c).as_int() == 1);
01218         assert(formula("2 and 0").execute(c).as_int() == 0);
01219         assert(formula("2 or 0").execute(c).as_int() == 1);
01220         assert(formula("-5").execute(c).as_int() == -5);
01221         assert(formula("not 5").execute(c).as_int() == 0);
01222         assert(formula("not 0").execute(c).as_int() == 1);
01223         assert(formula("abs(5)").execute(c).as_int() == 5);
01224         assert(formula("abs(-5)").execute(c).as_int() == 5);
01225         assert(formula("min(3,5)").execute(c).as_int() == 3);
01226         assert(formula("min(5,2)").execute(c).as_int() == 2);
01227         assert(formula("max(3,5)").execute(c).as_int() == 5);
01228         assert(formula("max(5,2)").execute(c).as_int() == 5);
01229         assert(formula("max(4,5,[2,18,7])").execute(c).as_int() == 18);
01230         assert(formula("char.strength").execute(p).as_int() == 15);
01231         assert(formula("choose(members,strength).strength").execute(p).as_int() == 16);
01232         assert(formula("4^2").execute().as_int() == 16);
01233         assert(formula("2+3^3").execute().as_int() == 29);
01234         assert(formula("2*3^3+2").execute().as_int() == 56);
01235         assert(formula("9^3").execute().as_int() == 729);
01236         assert(formula("x*5 where x=1").execute().as_int() == 5);
01237         assert(formula("x*(a*b where a=2,b=1) where x=5").execute().as_int() == 10);
01238         assert(formula("char.strength * ability where ability=3").execute(p).as_int() == 45);
01239         assert(formula("'abcd' = 'abcd'").execute(p).as_bool() == true);
01240         assert(formula("'abcd' = 'acd'").execute(p).as_bool() == false);
01241         assert(formula("'strength, agility: {strength}, {agility}'").execute(c).as_string() ==
01242                        "strength, agility: 15, 12");
01243         const int dice_roll = formula("3d6").execute().as_int();
01244         assert(dice_roll >= 3 && dice_roll <= 18);
01245 
01246         variant myarray = formula("[1,2,3]").execute();
01247         assert(myarray.num_elements() == 3);
01248         assert(myarray[0].as_int() == 1);
01249         assert(myarray[1].as_int() == 2);
01250         assert(myarray[2].as_int() == 3);
01251     } catch(formula_error& e) {
01252         std::cerr << "parse error\n";
01253     }
01254 }
01255 #endif
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Thu May 24 2012 01:02:36 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs