mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-11 01:24:24 +00:00
1256 lines
35 KiB
C++
1256 lines
35 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2007 by David White <dave.net>
|
|
Part of the Silver Tree Project
|
|
|
|
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 or later.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
#include "global.hpp"
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <iostream>
|
|
#include <set>
|
|
#include <sstream>
|
|
|
|
#include "foreach.hpp"
|
|
#include "formula_callable.hpp"
|
|
#include "formula_function.hpp"
|
|
#include "map_utils.hpp"
|
|
|
|
namespace game_logic
|
|
{
|
|
|
|
void formula_callable::set_value(const std::string& key, const variant& /*value*/)
|
|
{
|
|
std::cerr << "ERROR: cannot set key '" << key << "' on object\n";
|
|
}
|
|
|
|
|
|
map_formula_callable::map_formula_callable(
|
|
const formula_callable* fallback) :
|
|
formula_callable(false),
|
|
values_(),
|
|
fallback_(fallback)
|
|
{}
|
|
|
|
map_formula_callable& map_formula_callable::add(const std::string& key,
|
|
const variant& value)
|
|
{
|
|
values_[key] = value;
|
|
return *this;
|
|
}
|
|
|
|
variant map_formula_callable::get_value(const std::string& key) const
|
|
{
|
|
return map_get_value_default(values_, key,
|
|
fallback_ ? fallback_->query_value(key) : variant());
|
|
}
|
|
|
|
void map_formula_callable::get_inputs(std::vector<formula_input>* inputs) const
|
|
{
|
|
if(fallback_) {
|
|
fallback_->get_inputs(inputs);
|
|
}
|
|
for(std::map<std::string,variant>::const_iterator i = values_.begin(); i != values_.end(); ++i) {
|
|
inputs->push_back(formula_input(i->first, FORMULA_READ_WRITE));
|
|
}
|
|
}
|
|
|
|
void map_formula_callable::set_value(const std::string& key, const variant& value)
|
|
{
|
|
values_[key] = value;
|
|
}
|
|
|
|
namespace {
|
|
|
|
class function_list_expression : public formula_expression {
|
|
public:
|
|
explicit function_list_expression(function_symbol_table *symbols)
|
|
: symbols_(symbols)
|
|
{}
|
|
|
|
virtual std::string str() const
|
|
{
|
|
return "{function_list_expression()}";
|
|
}
|
|
private:
|
|
variant execute(const formula_callable& /*variables*/, formula_debugger * /*fdb*/) const {
|
|
std::vector<variant> res;
|
|
std::vector<std::string> function_names = builtin_function_names();
|
|
std::vector<std::string> more_function_names = symbols_->get_function_names();
|
|
function_names.insert(function_names.end(), more_function_names.begin(), more_function_names.end());
|
|
for(size_t i = 0; i < function_names.size(); i++) {
|
|
res.push_back(variant(function_names[i]));
|
|
}
|
|
return variant(&res);
|
|
}
|
|
|
|
function_symbol_table* symbols_;
|
|
};
|
|
|
|
class list_expression : public formula_expression {
|
|
public:
|
|
explicit list_expression(const std::vector<expression_ptr>& items)
|
|
: items_(items)
|
|
{}
|
|
|
|
private:
|
|
variant execute(const formula_callable& variables, formula_debugger *fdb) const {
|
|
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));
|
|
}
|
|
|
|
return variant(&res);
|
|
}
|
|
|
|
std::vector<expression_ptr> items_;
|
|
|
|
std::string str() const
|
|
{
|
|
std::stringstream s;
|
|
s << '[';
|
|
bool first_item = true;
|
|
foreach(expression_ptr a , items_) {
|
|
if (!first_item) {
|
|
s << ',';
|
|
} else {
|
|
first_item = false;
|
|
}
|
|
s << a->str();
|
|
}
|
|
s << ']';
|
|
return s.str();
|
|
}
|
|
|
|
|
|
};
|
|
|
|
class map_expression : public formula_expression {
|
|
public:
|
|
explicit map_expression(const std::vector<expression_ptr>& items)
|
|
: items_(items)
|
|
{}
|
|
|
|
virtual std::string str() const
|
|
{
|
|
std::stringstream s;
|
|
s << " [";
|
|
for(std::vector<expression_ptr>::const_iterator i = items_.begin(); ( i != items_.end() ) && ( i+1 != items_.end() ) ; i+=2) {
|
|
s << (*i)->str();
|
|
s << " -> ";
|
|
s << (*(i+1))->str();
|
|
}
|
|
s << " ]";
|
|
return s.str();
|
|
}
|
|
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);
|
|
res[ key ] = value;
|
|
}
|
|
|
|
return variant(&res);
|
|
}
|
|
|
|
std::vector<expression_ptr> items_;
|
|
};
|
|
|
|
class unary_operator_expression : public formula_expression {
|
|
public:
|
|
unary_operator_expression(const std::string& op, expression_ptr arg) :
|
|
op_(),op_str_(op),
|
|
operand_(arg)
|
|
{
|
|
if(op == "not") {
|
|
op_ = NOT;
|
|
} else if(op == "-") {
|
|
op_ = SUB;
|
|
} else {
|
|
throw formula_error("Illegal unary operator: '" + op + "'" , "", "", 0);
|
|
}
|
|
}
|
|
|
|
virtual std::string str() const {
|
|
std::stringstream s;
|
|
s << op_str_ << '('<< operand_->str() << ')';
|
|
return s.str();
|
|
}
|
|
|
|
private:
|
|
variant execute(const formula_callable& variables, formula_debugger *fdb) const {
|
|
const variant res = operand_->evaluate(variables,fdb);
|
|
switch(op_) {
|
|
case NOT:
|
|
return res.as_bool() ? variant(0) : variant(1);
|
|
case SUB:
|
|
default:
|
|
return -res;
|
|
}
|
|
}
|
|
enum OP { NOT, SUB };
|
|
OP op_;
|
|
std::string op_str_;
|
|
expression_ptr operand_;
|
|
};
|
|
|
|
class list_callable : public formula_callable {
|
|
variant list_;
|
|
public:
|
|
explicit list_callable(const variant& list) : list_(list)
|
|
{}
|
|
|
|
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));
|
|
inputs->push_back(formula_input("first", FORMULA_READ_WRITE));
|
|
inputs->push_back(formula_input("last", FORMULA_READ_WRITE));
|
|
}
|
|
|
|
variant get_value(const std::string& key) const {
|
|
if(key == "size") {
|
|
return variant(list_.num_elements());
|
|
} else if(key == "empty") {
|
|
return variant(list_.num_elements() == 0);
|
|
} else if(key == "first") {
|
|
if(list_.num_elements() > 0) {
|
|
return list_[0];
|
|
} else {
|
|
return variant();
|
|
}
|
|
} else if(key == "last") {
|
|
if(list_.num_elements() > 0) {
|
|
return list_[list_.num_elements()-1];
|
|
} else {
|
|
return variant();
|
|
}
|
|
} else {
|
|
return variant();
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
class dot_callable : public formula_callable {
|
|
public:
|
|
dot_callable(const formula_callable &global,
|
|
const formula_callable& local)
|
|
: global_(global), local_(local) { }
|
|
private:
|
|
const formula_callable& global_, &local_;
|
|
|
|
void get_inputs(std::vector<formula_input>* inputs) const {
|
|
return local_.get_inputs(inputs);
|
|
}
|
|
|
|
variant get_value(const std::string& key) const {
|
|
variant v = local_.query_value(key);
|
|
|
|
if ( v == variant() )
|
|
return global_.query_value(key);
|
|
else
|
|
return v;
|
|
}
|
|
};
|
|
|
|
class dot_expression : public formula_expression {
|
|
public:
|
|
dot_expression(expression_ptr left, expression_ptr right)
|
|
: left_(left), right_(right)
|
|
{}
|
|
std::string str() const
|
|
{
|
|
std::stringstream s;
|
|
s << left_->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."));
|
|
if(!left.is_callable()) {
|
|
if(left.is_list()) {
|
|
list_callable list_call(left);
|
|
dot_callable callable(variables, list_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"));
|
|
}
|
|
|
|
expression_ptr left_, right_;
|
|
};
|
|
|
|
class square_bracket_expression : public formula_expression {
|
|
public:
|
|
square_bracket_expression(expression_ptr left, expression_ptr key)
|
|
: left_(left), key_(key)
|
|
{}
|
|
|
|
std::string str() const
|
|
{
|
|
std::stringstream s;
|
|
s << left_->str() << '[' << key_->str() << ']';
|
|
return s.str();
|
|
}
|
|
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);
|
|
if(left.is_list() || left.is_map()) {
|
|
return left[ key ];
|
|
} else {
|
|
return variant();
|
|
}
|
|
}
|
|
|
|
expression_ptr left_, key_;
|
|
};
|
|
|
|
class operator_expression : public formula_expression {
|
|
public:
|
|
operator_expression(const std::string& op, expression_ptr left,
|
|
expression_ptr right)
|
|
: op_(OP(op[0])), op_str_(op), left_(left), right_(right)
|
|
{
|
|
if(op == ">=") {
|
|
op_ = GTE;
|
|
} else if(op == "<=") {
|
|
op_ = LTE;
|
|
} else if(op == "!=") {
|
|
op_ = NEQ;
|
|
} else if(op == "and") {
|
|
op_ = AND;
|
|
} else if(op == "or") {
|
|
op_ = OR;
|
|
} else if(op == ".+") {
|
|
op_ = ADDL;
|
|
} else if(op == ".-") {
|
|
op_ = SUBL;
|
|
} else if(op == ".*") {
|
|
op_ = MULL;
|
|
} else if(op == "./") {
|
|
op_ = DIVL;
|
|
}
|
|
}
|
|
|
|
std::string str() const
|
|
{
|
|
std::stringstream s;
|
|
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"));
|
|
switch(op_) {
|
|
case AND:
|
|
return left.as_bool() == false ? left : right;
|
|
case OR:
|
|
return left.as_bool() ? left : right;
|
|
case ADD:
|
|
return left + right;
|
|
case SUB:
|
|
return left - right;
|
|
case MUL:
|
|
return left * right;
|
|
case DIV:
|
|
return left / right;
|
|
case POW:
|
|
return left ^ right;
|
|
case ADDL:
|
|
return left.list_elements_add(right);
|
|
case SUBL:
|
|
return left.list_elements_sub(right);
|
|
case MULL:
|
|
return left.list_elements_mul(right);
|
|
case DIVL:
|
|
return left.list_elements_div(right);
|
|
case EQ:
|
|
return left == right ? variant(1) : variant(0);
|
|
case NEQ:
|
|
return left != right ? variant(1) : variant(0);
|
|
case LTE:
|
|
return left <= right ? variant(1) : variant(0);
|
|
case GTE:
|
|
return left >= right ? variant(1) : variant(0);
|
|
case LT:
|
|
return left < right ? variant(1) : variant(0);
|
|
case GT:
|
|
return left > right ? variant(1) : variant(0);
|
|
case MOD:
|
|
return left % right;
|
|
case DICE:
|
|
default:
|
|
return variant(dice_roll(left.as_int(), right.as_int()));
|
|
}
|
|
}
|
|
|
|
static int dice_roll(int num_rolls, int faces) {
|
|
int res = 0;
|
|
while(faces > 0 && num_rolls-- > 0) {
|
|
res += (rand()%faces)+1;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
enum OP { AND, OR, NEQ, LTE, GTE, GT='>', LT='<', EQ='=',
|
|
ADD='+', SUB='-', MUL='*', DIV='/', ADDL, SUBL, MULL, DIVL, DICE='d', POW='^', MOD='%' };
|
|
|
|
OP op_;
|
|
std::string op_str_;
|
|
expression_ptr left_, right_;
|
|
};
|
|
|
|
typedef std::map<std::string,expression_ptr> expr_table;
|
|
typedef boost::shared_ptr<expr_table> expr_table_ptr;
|
|
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 )
|
|
: formula_callable(false)
|
|
, base_(base)
|
|
, table_(table)
|
|
, evaluated_table_()
|
|
{
|
|
}
|
|
|
|
private:
|
|
const formula_callable& base_;
|
|
expr_table_ptr table_;
|
|
mutable exp_table_evaluated evaluated_table_;
|
|
|
|
void get_inputs(std::vector<formula_input>* inputs) const {
|
|
for(expr_table::const_iterator i = table_->begin(); i != table_->end(); ++i) {
|
|
inputs->push_back(formula_input(i->first, FORMULA_READ_ONLY));
|
|
}
|
|
}
|
|
|
|
variant get_value(const std::string& key) const {
|
|
expr_table::iterator i = table_->find(key);
|
|
if(i != table_->end()) {
|
|
exp_table_evaluated::const_iterator ev = evaluated_table_.find(key);
|
|
if( ev != evaluated_table_.end())
|
|
return ev->second;
|
|
|
|
variant v = i->second->evaluate(base_);
|
|
evaluated_table_[key] = v;
|
|
return v;
|
|
}
|
|
return base_.query_value(key);
|
|
}
|
|
};
|
|
|
|
class where_expression: public formula_expression {
|
|
public:
|
|
explicit where_expression(expression_ptr body,
|
|
expr_table_ptr clauses)
|
|
: body_(body), clauses_(clauses)
|
|
{}
|
|
|
|
std::string str() const
|
|
{
|
|
std::stringstream s;
|
|
s << "{where:(";
|
|
s << body_->str();
|
|
foreach (const expr_table::value_type &a, *clauses_) {
|
|
s << ", [" << a.first << "] -> ["<< a.second->str()<<"]";
|
|
}
|
|
s << ")}";
|
|
return s.str();
|
|
}
|
|
|
|
private:
|
|
expression_ptr body_;
|
|
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);
|
|
}
|
|
};
|
|
|
|
|
|
class identifier_expression : public formula_expression {
|
|
public:
|
|
explicit identifier_expression(const std::string& id) : id_(id)
|
|
{}
|
|
std::string str() const
|
|
{
|
|
return id_;
|
|
}
|
|
private:
|
|
variant execute(const formula_callable& variables, formula_debugger * /*fdb*/) const {
|
|
return variables.query_value(id_);
|
|
}
|
|
std::string id_;
|
|
};
|
|
|
|
class null_expression : public formula_expression {
|
|
public:
|
|
explicit null_expression() {};
|
|
std::string str() const {
|
|
return "";
|
|
}
|
|
private:
|
|
variant execute(const formula_callable& /*variables*/, formula_debugger * /*fdb*/) const {
|
|
return variant();
|
|
}
|
|
};
|
|
|
|
|
|
class integer_expression : public formula_expression {
|
|
public:
|
|
explicit integer_expression(int i) : i_(i)
|
|
{}
|
|
std::string str() const
|
|
{
|
|
std::stringstream s;
|
|
s << i_;
|
|
return s.str();
|
|
}
|
|
private:
|
|
variant execute(const formula_callable& /*variables*/, formula_debugger * /*fdb*/) const {
|
|
return variant(i_);
|
|
}
|
|
|
|
int i_;
|
|
};
|
|
|
|
class decimal_expression : public formula_expression {
|
|
public:
|
|
explicit decimal_expression(int i, int f) : i_(i), f_(f)
|
|
{}
|
|
|
|
std::string str() const
|
|
{
|
|
std::stringstream s;
|
|
s << i_ << '.' << f_;
|
|
return s.str();
|
|
}
|
|
private:
|
|
variant execute(const formula_callable& /*variables*/, formula_debugger * /*fdb*/) const {
|
|
return variant(i_ * 1000 + f_, variant::DECIMAL_VARIANT );
|
|
}
|
|
|
|
int i_, f_;
|
|
};
|
|
|
|
class string_expression : public formula_expression {
|
|
public:
|
|
explicit string_expression(std::string str) :
|
|
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(), '}');
|
|
if(j == str.end()) {
|
|
break;
|
|
}
|
|
|
|
const std::string formula_str(i+1, j);
|
|
const int pos = i - str.begin();
|
|
str.erase(i, j+1);
|
|
|
|
substitution sub;
|
|
sub.pos = pos;
|
|
sub.calculation.reset(new formula(formula_str));
|
|
subs_.push_back(sub);
|
|
}
|
|
|
|
std::reverse(subs_.begin(), subs_.end());
|
|
|
|
str_ = variant(str);
|
|
}
|
|
|
|
std::string str() const
|
|
{
|
|
return str_.as_string();
|
|
}
|
|
private:
|
|
variant execute(const formula_callable& variables, formula_debugger *fdb) const {
|
|
if(subs_.empty()) {
|
|
return str_;
|
|
} else {
|
|
std::string res = str_.as_string();
|
|
for(size_t i=0; i < subs_.size(); ++i) {
|
|
const substitution& sub = subs_[i];
|
|
const std::string str = sub.calculation->evaluate(variables,fdb).string_cast();
|
|
res.insert(sub.pos, str);
|
|
}
|
|
|
|
return variant(res);
|
|
}
|
|
}
|
|
|
|
struct substitution {
|
|
|
|
substitution() :
|
|
pos(0),
|
|
calculation()
|
|
{
|
|
}
|
|
|
|
int pos;
|
|
const_formula_ptr calculation;
|
|
};
|
|
|
|
variant str_;
|
|
std::vector<substitution> subs_;
|
|
};
|
|
|
|
using namespace formula_tokenizer;
|
|
int operator_precedence(const token& t)
|
|
{
|
|
static std::map<std::string,int> precedence_map;
|
|
if(precedence_map.empty()) {
|
|
int n = 0;
|
|
precedence_map["not"] = ++n;
|
|
precedence_map["where"] = ++n;
|
|
precedence_map["or"] = ++n;
|
|
precedence_map["and"] = ++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;
|
|
precedence_map["/"] = ++n;
|
|
precedence_map["%"] = ++n;
|
|
precedence_map["^"] = ++n;
|
|
precedence_map["d"] = ++n;
|
|
precedence_map["."] = ++n;
|
|
}
|
|
|
|
assert(precedence_map.count(std::string(t.begin,t.end)));
|
|
return precedence_map[std::string(t.begin,t.end)];
|
|
}
|
|
|
|
expression_ptr parse_expression(const token* i1, const token* i2, function_symbol_table* symbols);
|
|
|
|
|
|
//function used when creating error reports, parses all tokens passed to parse_expression, thus there are no EOL or whitespaces
|
|
std::string tokens_to_string(const token* i1, const token* i2)
|
|
{
|
|
std::ostringstream expr;
|
|
while(i1 != i2) {
|
|
expr << std::string(i1->begin,i1->end) << " ";
|
|
++i1;
|
|
}
|
|
return expr.str();
|
|
}
|
|
|
|
void parse_function_args(const token* &i1, const token* i2,
|
|
std::vector<std::string>* res)
|
|
{
|
|
const token* begin = i1, *end = i2; //these are used for error reporting
|
|
|
|
if(i1->type == TOKEN_LPARENS) {
|
|
++i1;
|
|
} else {
|
|
throw formula_error("Invalid function definition", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
|
|
}
|
|
|
|
while((i1-> type != TOKEN_RPARENS) && (i1 != i2)) {
|
|
if(i1->type == TOKEN_IDENTIFIER) {
|
|
if(std::string((i1+1)->begin, (i1+1)->end) == "*") {
|
|
res->push_back(std::string(i1->begin, i1->end) + std::string("*"));
|
|
++i1;
|
|
} else {
|
|
res->push_back(std::string(i1->begin, i1->end));
|
|
}
|
|
} else if (i1->type == TOKEN_COMMA) {
|
|
//do nothing
|
|
} else {
|
|
throw formula_error("Invalid function definition", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
|
|
}
|
|
++i1;
|
|
}
|
|
|
|
if(i1->type != TOKEN_RPARENS) {
|
|
throw formula_error("Invalid function definition", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
|
|
}
|
|
++i1;
|
|
}
|
|
|
|
void parse_args(const token* i1, const token* i2,
|
|
std::vector<expression_ptr>* res,
|
|
function_symbol_table* symbols)
|
|
{
|
|
int parens = 0;
|
|
const token* beg = i1;
|
|
while(i1 != i2) {
|
|
if(i1->type == TOKEN_LPARENS || i1->type == TOKEN_LSQUARE ) {
|
|
++parens;
|
|
} else if(i1->type == TOKEN_RPARENS || i1->type == TOKEN_RSQUARE ) {
|
|
--parens;
|
|
} else if(i1->type == TOKEN_COMMA && !parens) {
|
|
res->push_back(parse_expression(beg,i1, symbols));
|
|
beg = i1+1;
|
|
}
|
|
++i1;
|
|
}
|
|
|
|
if(beg != i1) {
|
|
res->push_back(parse_expression(beg,i1, symbols));
|
|
}
|
|
}
|
|
|
|
void parse_set_args(const token* i1, const token* i2,
|
|
std::vector<expression_ptr>* res,
|
|
function_symbol_table* symbols)
|
|
{
|
|
int parens = 0;
|
|
bool check_pointer = false;
|
|
const token* beg = i1;
|
|
const token* begin = i1, *end = i2; //these are used for error reporting
|
|
while(i1 != i2) {
|
|
if(i1->type == TOKEN_LPARENS || i1->type == TOKEN_LSQUARE) {
|
|
++parens;
|
|
} else if(i1->type == TOKEN_RPARENS || i1->type == TOKEN_RSQUARE) {
|
|
--parens;
|
|
} else if( i1->type == TOKEN_POINTER && !parens ) {
|
|
if (!check_pointer) {
|
|
check_pointer = true;
|
|
res->push_back(parse_expression(beg,i1, symbols));
|
|
beg = i1+1;
|
|
} else {
|
|
throw formula_error("Too many '->' operators found", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
|
|
}
|
|
} else if( i1->type == TOKEN_COMMA && !parens ) {
|
|
if (check_pointer)
|
|
check_pointer = false;
|
|
else {
|
|
throw formula_error("Expected comma, but '->' found", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
|
|
}
|
|
res->push_back(parse_expression(beg,i1, symbols));
|
|
beg = i1+1;
|
|
}
|
|
|
|
++i1;
|
|
}
|
|
|
|
if(beg != i1) {
|
|
res->push_back(parse_expression(beg,i1, symbols));
|
|
}
|
|
}
|
|
|
|
void parse_where_clauses(const token* i1, const token * i2,
|
|
expr_table_ptr res, function_symbol_table* symbols) {
|
|
int parens = 0;
|
|
const token *original_i1_cached = i1;
|
|
const token *beg = i1;
|
|
const token* begin = i1, *end = i2; //these are used for error reporting
|
|
std::string var_name;
|
|
while(i1 != i2) {
|
|
if(i1->type == TOKEN_LPARENS) {
|
|
++parens;
|
|
} else if(i1->type == TOKEN_RPARENS) {
|
|
--parens;
|
|
} else if(!parens) {
|
|
if(i1->type == TOKEN_COMMA) {
|
|
if(var_name.empty()) {
|
|
throw formula_error("There is 'where <expression>' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
|
|
}
|
|
(*res)[var_name] = parse_expression(beg,i1, symbols);
|
|
beg = i1+1;
|
|
var_name = "";
|
|
} else if(i1->type == TOKEN_OPERATOR) {
|
|
std::string op_name(i1->begin, i1->end);
|
|
if(op_name == "=") {
|
|
if(beg->type != TOKEN_IDENTIFIER) {
|
|
if(i1 == original_i1_cached) {
|
|
throw formula_error("There is 'where <expression' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
|
|
} else {
|
|
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);
|
|
}
|
|
} else if(beg+1 != i1) {
|
|
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);
|
|
} else if(!var_name.empty()) {
|
|
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);
|
|
}
|
|
var_name.insert(var_name.end(), beg->begin, beg->end);
|
|
beg = i1+1;
|
|
}
|
|
}
|
|
}
|
|
++i1;
|
|
}
|
|
if(beg != i1) {
|
|
if(var_name.empty()) {
|
|
throw formula_error("There is 'where <expression>' but 'where name=<expression>' was needed", tokens_to_string(begin,end-1), *i1->filename, i1->line_number);
|
|
}
|
|
(*res)[var_name] = parse_expression(beg,i1, symbols);
|
|
}
|
|
}
|
|
|
|
expression_ptr parse_expression(const token* i1, const token* i2, function_symbol_table* symbols)
|
|
{
|
|
if(i1 == i2) {
|
|
throw formula_error("Empty expression", "", *i1->filename, i1->line_number);
|
|
}
|
|
|
|
const token* begin = i1, *end = i2; //these are used for error reporting
|
|
|
|
if(i1->type == TOKEN_KEYWORD &&
|
|
(i1+1)->type == TOKEN_IDENTIFIER) {
|
|
if(std::string(i1->begin, i1->end) == "def") {
|
|
++i1;
|
|
const std::string formula_name = std::string(i1->begin, i1->end);
|
|
std::vector<std::string> args;
|
|
parse_function_args(++i1, i2, &args);
|
|
const token* beg = i1;
|
|
while((i1 != i2) && (i1->type != TOKEN_SEMICOLON)) {
|
|
++i1;
|
|
}
|
|
const std::string precond = "";
|
|
if(symbols == NULL) {
|
|
throw formula_error("Function symbol table required but not present", "",*i1->filename, i1->line_number);
|
|
}
|
|
symbols->add_formula_function(formula_name,
|
|
const_formula_ptr(new formula(beg, i1, symbols)),
|
|
formula::create_optional_formula(precond, symbols),
|
|
args);
|
|
if((i1 == i2) || (i1 == (i2-1))) {
|
|
return expression_ptr(new function_list_expression(symbols));
|
|
}
|
|
else {
|
|
return parse_expression((i1+1), i2, symbols);
|
|
}
|
|
}
|
|
}
|
|
|
|
int parens = 0;
|
|
const token* op = NULL;
|
|
bool operator_group = false;
|
|
for(const token* i = i1; i != i2; ++i) {
|
|
if(i->type == TOKEN_LPARENS || i->type == TOKEN_LSQUARE) {
|
|
++parens;
|
|
} else if(i->type == TOKEN_RPARENS || i->type == TOKEN_RSQUARE) {
|
|
--parens;
|
|
} else if(parens == 0 && i->type == TOKEN_OPERATOR) {
|
|
if( ( !operator_group ) && (op == NULL || operator_precedence(*op) >=
|
|
operator_precedence(*i)) ) {
|
|
op = i;
|
|
}
|
|
operator_group = true;
|
|
} else {
|
|
operator_group = false;
|
|
}
|
|
}
|
|
|
|
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 - i1 == 1) {
|
|
if(i1->type == TOKEN_KEYWORD) {
|
|
if(std::string(i1->begin,i1->end) == "functions") {
|
|
return expression_ptr(new function_list_expression(symbols));
|
|
}
|
|
} else if(i1->type == TOKEN_IDENTIFIER) {
|
|
return expression_ptr(new identifier_expression(
|
|
std::string(i1->begin,i1->end)));
|
|
} else if(i1->type == TOKEN_INTEGER) {
|
|
int n = atoi(std::string(i1->begin,i1->end).c_str());
|
|
return expression_ptr(new integer_expression(n));
|
|
} else if(i1->type == TOKEN_DECIMAL) {
|
|
iterator dot = i1->begin;
|
|
while( *dot != '.' )
|
|
++dot;
|
|
|
|
int n = atoi(std::string(i1->begin,dot).c_str());
|
|
|
|
iterator end = i1->end;
|
|
|
|
if( end - dot > 4)
|
|
end = dot + 4;
|
|
|
|
++dot;
|
|
|
|
int f = 0;
|
|
|
|
int multiplicator = 100;
|
|
while( dot != end) {
|
|
f += (*dot - 48)*multiplicator;
|
|
multiplicator /= 10;
|
|
++dot;
|
|
}
|
|
|
|
return expression_ptr(new decimal_expression(n, f));
|
|
} else if(i1->type == TOKEN_STRING_LITERAL) {
|
|
return expression_ptr(new string_expression(std::string(i1->begin+1,i1->end-1)));
|
|
}
|
|
} else if(i1->type == TOKEN_IDENTIFIER &&
|
|
(i1+1)->type == TOKEN_LPARENS &&
|
|
(i2-1)->type == TOKEN_RPARENS) {
|
|
const token* begin = i1, *end = i2; //these are used for error reporting
|
|
int nleft = 0, nright = 0;
|
|
for(const token* i = i1; i != i2; ++i) {
|
|
if(i->type == TOKEN_LPARENS) {
|
|
++nleft;
|
|
} else if(i->type == TOKEN_RPARENS) {
|
|
++nright;
|
|
}
|
|
}
|
|
|
|
if(nleft == nright) {
|
|
std::vector<expression_ptr> args;
|
|
parse_args(i1+2,i2-1,&args,symbols);
|
|
try{
|
|
return expression_ptr( create_function(std::string(i1->begin,i1->end),args,symbols) );
|
|
}
|
|
catch(formula_error& e) {
|
|
throw formula_error(e.type, tokens_to_string(begin,end), *i1->filename, i1->line_number);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw formula_error("Could not parse expression", tokens_to_string(i1, i2), *i1->filename, i1->line_number);
|
|
}
|
|
if (op + 1 == end) {
|
|
throw formula_error("Expected another token", tokens_to_string(begin,end-1), *op->filename, op->line_number);
|
|
}
|
|
if(op == i1) {
|
|
try{
|
|
return expression_ptr(new unary_operator_expression(
|
|
std::string(op->begin,op->end),
|
|
parse_expression(op+1,i2,symbols)));
|
|
}
|
|
catch(formula_error& e) {
|
|
throw formula_error( e.type, tokens_to_string(begin,end-1), *op->filename, op->line_number);
|
|
}
|
|
}
|
|
|
|
const std::string op_name(op->begin,op->end);
|
|
|
|
if(op_name == ".") {
|
|
return expression_ptr(new dot_expression(
|
|
parse_expression(i1,op,symbols),
|
|
parse_expression(op+1,i2,symbols)));
|
|
}
|
|
|
|
if(op_name == "where") {
|
|
expr_table_ptr table(new expr_table());
|
|
parse_where_clauses(op+1, i2, table, symbols);
|
|
return expression_ptr(new where_expression(parse_expression(i1, op, symbols),
|
|
table));
|
|
}
|
|
|
|
return expression_ptr(new operator_expression(
|
|
op_name, parse_expression(i1,op,symbols),
|
|
parse_expression(op+1,i2,symbols)));
|
|
}
|
|
|
|
}
|
|
|
|
formula_ptr formula::create_optional_formula(const std::string& str, function_symbol_table* symbols)
|
|
{
|
|
if(str.empty()) {
|
|
return formula_ptr();
|
|
}
|
|
|
|
return formula_ptr(new formula(str, symbols));
|
|
}
|
|
|
|
formula::formula(const std::string& str, function_symbol_table* symbols) :
|
|
expr_(),
|
|
str_(str)
|
|
{
|
|
using namespace formula_tokenizer;
|
|
|
|
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;
|
|
//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
|
|
std::set< std::string > filenames;
|
|
files.push_back( std::make_pair( "formula", 1 ) );
|
|
filenames.insert( "formula" );
|
|
std::set< std::string >::iterator filenames_it = filenames.begin();
|
|
|
|
while(i1 != i2) {
|
|
try {
|
|
|
|
tokens.push_back( get_token(i1,i2) );
|
|
|
|
TOKEN_TYPE current_type = tokens.back().type;
|
|
|
|
if(current_type == TOKEN_WHITESPACE) {
|
|
tokens.pop_back();
|
|
} else
|
|
if( current_type == TOKEN_COMMENT) {
|
|
//since we can have multiline comments, let's see how many EOL are within it
|
|
int counter = 0;
|
|
std::string comment = std::string(tokens.back().begin,tokens.back().end);
|
|
for( std::string::iterator str_it = comment.begin(); str_it != comment.end(); ++str_it)
|
|
if( *str_it == '\n' )
|
|
counter++;
|
|
|
|
files.back().second+=counter;
|
|
tokens.pop_back();
|
|
} else
|
|
if( current_type == TOKEN_EOL) {
|
|
files.back().second++;
|
|
tokens.pop_back();
|
|
} else
|
|
if( ( current_type == TOKEN_KEYWORD) && ( std::string(tokens.back().begin,tokens.back().end) == "fai") ) {
|
|
fai_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();
|
|
filenames_it = filenames.find( files.back().first );
|
|
tokens.pop_back();
|
|
} else {
|
|
throw formula_error("Unexpected 'faiend' found", "", "", 0);
|
|
}
|
|
} else
|
|
if (fai_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 ) );
|
|
std::pair < std::set< std::string>::iterator , bool > ret;
|
|
ret = filenames.insert( str );
|
|
if(ret.second==true) {
|
|
filenames_it = ret.first;
|
|
} else {
|
|
throw formula_error("Faifile already included", "fai" + str, "", 0);
|
|
}
|
|
tokens.pop_back();
|
|
fai_keyword = false;
|
|
} else {
|
|
throw formula_error("Expected string after the 'fai'", "fai", "", 0);
|
|
}
|
|
} else {
|
|
//in every token not specified above, store line number and name of file it came from
|
|
tokens.back().filename = &(*filenames_it);
|
|
tokens.back().line_number = files.back().second;
|
|
}
|
|
} catch(token_error& e) {
|
|
//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
|
|
std::string str = "";
|
|
if(!tokens.empty()) {
|
|
token* tok_it = &tokens[0] + tokens.size()-1;
|
|
while( ( tok_it != &tokens[0] ) && (tok_it->line_number == tokens.back().line_number) )
|
|
--tok_it;
|
|
|
|
if( tok_it != &tokens[0] && tok_it != &tokens[0] + tokens.size() -1)
|
|
++tok_it;
|
|
|
|
str = tokens_to_string( tok_it, &tokens[0] + tokens.size() );
|
|
}
|
|
|
|
throw formula_error(e.description_, str + e.formula_, *filenames_it, files.back().second);
|
|
}
|
|
}
|
|
|
|
if(files.size() > 1) {
|
|
throw formula_error("Missing 'faiend', make sure each .fai file ends with it", "", "", 0);
|
|
}
|
|
|
|
if(!tokens.empty()) {
|
|
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_()
|
|
{
|
|
|
|
if(i1 != i2) {
|
|
expr_ = parse_expression(i1,i2, symbols);
|
|
} else {
|
|
expr_ = expression_ptr(new null_expression());
|
|
}
|
|
}
|
|
|
|
variant formula::execute(const formula_callable& variables, formula_debugger *fdb) const
|
|
{
|
|
try {
|
|
return expr_->evaluate(variables, fdb);
|
|
} catch(type_error& e) {
|
|
std::cerr << "formula type error: " << e.message << "\n";
|
|
return variant();
|
|
}
|
|
}
|
|
|
|
variant formula::execute(formula_debugger *fdb) const
|
|
{
|
|
static map_formula_callable null_callable;
|
|
return execute(null_callable,fdb);
|
|
}
|
|
|
|
formula_error::formula_error(const std::string& type, const std::string& formula,
|
|
const std::string& file, int line)
|
|
: error()
|
|
, type(type)
|
|
, formula(formula)
|
|
, filename(file)
|
|
, line(line)
|
|
{
|
|
std::stringstream ss;
|
|
ss << "Formula error in " << filename << ":" << line
|
|
<< "\nIn formula " << formula
|
|
<< "\nError: " << type;
|
|
message = ss.str();
|
|
}
|
|
|
|
}
|
|
|
|
#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
|