Skip to content

Commit

Permalink
math_parser: evaluate variables in diag. functions
Browse files Browse the repository at this point in the history
  • Loading branch information
andrei8l committed May 4, 2023
1 parent dff5c34 commit 168933f
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 43 deletions.
8 changes: 8 additions & 0 deletions data/mods/TEST_DATA/EOC.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@
"global": true,
"effect": { "u_message": "test recurrence" }
},
{
"type": "effect_on_condition",
"id": "EOC_math_diag_w_vars",
"effect": [
{ "set_string_var": "survival", "target_var": { "global_val": "myskill" } },
{ "math": [ "myskill_math", "=", "u_skill(myskill)" ] }
]
},
{
"type": "effect_on_condition",
"id": "EOC_jmath_test",
Expand Down
8 changes: 8 additions & 0 deletions src/cata_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,4 +667,12 @@ T aggregate( const std::vector<T> &values, aggregate_type agg_func )
}
}

// overload pattern for std::variant from https://en.cppreference.com/w/cpp/utility/variant/visit
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
explicit overloaded( Ts... ) -> overloaded<Ts...>;

#endif // CATA_SRC_CATA_UTILITY_H
42 changes: 26 additions & 16 deletions src/math_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ class math_exp::math_exp_impl
void maybe_first_argument();
void error( std::string_view str, std::string_view what );
void validate_string( std::string_view str, std::string_view label, std::string_view badlist );
std::vector<std::string> _get_strings( std::vector<thingie> const &params,
size_t nparams ) const;
std::vector<diag_func_val> _get_diag_vals( std::vector<thingie> const &params,
size_t nparams ) const;
};

void math_exp::math_exp_impl::maybe_first_argument()
Expand Down Expand Up @@ -474,12 +474,12 @@ void math_exp::math_exp_impl::new_func()
std::visit( overloaded{
[&params, nparams, this]( scoped_diag_eval const & v )
{
std::vector<std::string> const strings = _get_strings( params, nparams );
std::vector<diag_func_val> const strings = _get_diag_vals( params, nparams );
output.emplace( std::in_place_type_t<func_diag_eval>(), v.df->f( v.scope, strings ) );
},
[&params, nparams, this]( scoped_diag_ass const & v )
{
std::vector<std::string> const strings = _get_strings( params, nparams );
std::vector<diag_func_val> const strings = _get_diag_vals( params, nparams );
output.emplace( std::in_place_type_t<func_diag_ass>(), v.df->f( v.scope, strings ) );
},
[&params, this]( pmath_func v )
Expand All @@ -500,20 +500,30 @@ void math_exp::math_exp_impl::new_func()
}
}

std::vector<std::string> math_exp::math_exp_impl::_get_strings( std::vector<thingie> const
std::vector<diag_func_val> math_exp::math_exp_impl::_get_diag_vals( std::vector<thingie> const
&params, size_t nparams ) const
{
std::vector<std::string> strings( nparams );
std::transform( params.begin(), params.end(), strings.begin(), [this]( thingie const & e ) {
if( std::holds_alternative<std::string>( e.data ) ) {
return std::get<std::string>( e.data );
}
throw std::invalid_argument( string_format(
"Parameters for %s() must be strings contained in single quotes",
arity.top().sym.data() ) );
return std::string{};
} );
return strings;
std::vector<diag_func_val> vals( nparams );
for( decltype( vals )::size_type i = 0; i < params.size(); i++ ) {
std::visit( overloaded{
[&vals, i]( std::string const & v )
{
vals[i].data.emplace<std::string>( v );
},
[&vals, i]( var const & v )
{
vals[i].data.emplace<var_info>( v.varinfo );
},
[this]( auto /* v */ )
{
throw std::invalid_argument(
string_format( "Parameters for %s() must be variables or strings contained in single quotes",
arity.top().sym ) );
},
},
params[i].data );
}
return vals;
}

void math_exp::math_exp_impl::new_oper()
Expand Down
48 changes: 43 additions & 5 deletions src/math_parser_diag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
#include <string>
#include <vector>

#include "cata_utility.h"
#include "condition.h"
#include "dialogue.h"
#include "math_parser_shim.h"
#include "mission.h"

namespace
{
Expand All @@ -23,8 +23,38 @@ bool is_beta( char scope )
}
} // namespace

std::string_view diag_func_val::str() const
{
return std::visit( overloaded{
[]( std::string const & v ) -> std::string const &
{
return v;
},
[]( var_info const & v ) -> std::string const &
{
return v.name;
},
},
data );
}

std::string diag_func_val::eval( dialogue const &d ) const
{
return std::visit( overloaded{
[]( std::string const & v )
{
return v;
},
[&d]( var_info const & v )
{
return read_var_value( v, d );
},
},
data );
}

std::function<double( dialogue & )> u_val( char scope,
std::vector<std::string> const &params )
std::vector<diag_func_val> const &params )
{
kwargs_shim const shim( params, scope );
try {
Expand All @@ -38,7 +68,7 @@ std::function<double( dialogue & )> u_val( char scope,
}

std::function<void( dialogue &, double )> u_val_ass( char scope,
std::vector<std::string> const &params )
std::vector<diag_func_val> const &params )
{
kwargs_shim const shim( params, scope );
try {
Expand All @@ -50,17 +80,25 @@ std::function<void( dialogue &, double )> u_val_ass( char scope,
}

std::function<double( dialogue & )> pain_eval( char scope,
std::vector<std::string> const &/* params */ )
std::vector<diag_func_val> const &/* params */ )
{
return [beta = is_beta( scope )]( dialogue const & d ) {
return d.actor( beta )->pain_cur();
};
}

std::function<void( dialogue &, double )> pain_ass( char scope,
std::vector<std::string> const &/* params */ )
std::vector<diag_func_val> const &/* params */ )
{
return [beta = is_beta( scope )]( dialogue const & d, double val ) {
d.actor( beta )->set_pain( val );
};
}

std::function<double( dialogue & )> skill_eval( char scope,
std::vector<diag_func_val> const &params )
{
return [beta = is_beta( scope ), sid = params[0] ]( dialogue const & d ) {
return d.actor( beta )->get_skill_level( skill_id( sid.eval( d ) ) );
};
}
34 changes: 27 additions & 7 deletions src/math_parser_diag.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
#include <array>
#include <functional>
#include <string_view>
#include <variant>
#include <vector>

#include "dialogue_helpers.h"

struct dialogue;
struct dialogue_func {
dialogue_func( std::string_view s_, std::string_view sc_, int n_ ) : symbol( s_ ),
Expand All @@ -15,9 +18,22 @@ struct dialogue_func {
int num_params{};
};

struct diag_func_val {
std::string_view str() const;
std::string eval( dialogue const &d ) const;

using impl_t = std::variant<std::string, var_info>;
impl_t data;
};

constexpr bool operator==( diag_func_val const &lhs, std::string_view rhs )
{
return std::holds_alternative<std::string>( lhs.data ) && std::get<std::string>( lhs.data ) == rhs;
}

struct dialogue_func_eval : dialogue_func {
using f_t = std::function<double( dialogue & )> ( * )( char scope,
std::vector<std::string> const & );
std::vector<diag_func_val> const & );

dialogue_func_eval( std::string_view s_, std::string_view sc_, int n_, f_t f_ )
: dialogue_func( s_, sc_, n_ ), f( f_ ) {}
Expand All @@ -27,7 +43,7 @@ struct dialogue_func_eval : dialogue_func {

struct dialogue_func_ass : dialogue_func {
using f_t = std::function<void( dialogue &, double )> ( * )( char scope,
std::vector<std::string> const & );
std::vector<diag_func_val> const & );

dialogue_func_ass( std::string_view s_, std::string_view sc_, int n_, f_t f_ )
: dialogue_func( s_, sc_, n_ ), f( f_ ) {}
Expand All @@ -39,19 +55,23 @@ using pdiag_func_eval = dialogue_func_eval const *;
using pdiag_func_ass = dialogue_func_ass const *;

std::function<double( dialogue & )> u_val( char scope,
std::vector<std::string> const &params );
std::vector<diag_func_val> const &params );
std::function<void( dialogue &, double )> u_val_ass( char scope,
std::vector<std::string> const &params );
std::vector<diag_func_val> const &params );

std::function<double( dialogue & )> pain_eval( char scope,
std::vector<std::string> const &/* params */ );
std::vector<diag_func_val> const &/* params */ );

std::function<void( dialogue &, double )> pain_ass( char scope,
std::vector<std::string> const &/* params */ );
std::vector<diag_func_val> const &/* params */ );

std::function<double( dialogue & )> skill_eval( char scope,
std::vector<diag_func_val> const &params );

inline std::array<dialogue_func_eval, 2> const dialogue_eval_f{
inline std::array<dialogue_func_eval, 3> const dialogue_eval_f{
dialogue_func_eval{ "val", "un", -1, u_val },
dialogue_func_eval{ "pain", "un", 0, pain_eval },
dialogue_func_eval{ "skill", "un", 1, skill_eval },
};

inline std::array<dialogue_func_ass, 2> const dialogue_assign_f{
Expand Down
8 changes: 1 addition & 7 deletions src/math_parser_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <variant>
#include <vector>

#include "cata_utility.h"
#include "debug.h"
#include "dialogue_helpers.h"
#include "math_parser_diag.h"
Expand Down Expand Up @@ -131,13 +132,6 @@ struct thingie {
impl_t data;
};

// overload pattern from https://en.cppreference.com/w/cpp/utility/variant/visit
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
explicit overloaded( Ts... ) -> overloaded<Ts...>;
constexpr double thingie::eval( dialogue &d ) const
{
return std::visit( overloaded{
Expand Down
10 changes: 5 additions & 5 deletions src/math_parser_shim.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#include "math_parser_shim.h"
#include "math_parser_diag.h"
#include "math_parser_func.h"

#include <map>
#include <string_view>

#include "condition.h"
#include "json_loader.h"
#include "string_formatter.h"

kwargs_shim::kwargs_shim( std::vector<std::string> const &tokens, char scope )
kwargs_shim::kwargs_shim( std::vector<diag_func_val> const &tokens, char scope )
{
bool positional = false;
for( std::string_view const token : tokens ) {
std::vector<std::string_view> parts = tokenize( token, ":", false );
for( diag_func_val const &token : tokens ) {
std::vector<std::string_view> parts = tokenize( token.str(), ":", false );
if( parts.size() == 1 && !positional ) {
kwargs.emplace(
// NOLINTNEXTLINE(cata-translate-string-literal): not a user-visible string
Expand All @@ -22,7 +22,7 @@ kwargs_shim::kwargs_shim( std::vector<std::string> const &tokens, char scope )
} else if( parts.size() == 2 ) {
kwargs.emplace( parts[0], parts[1] );
} else {
debugmsg( "Too many parts in token %.*s", token.size(), token.data() );
debugmsg( "Too many parts in token %s", token.str() );
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/math_parser_shim.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
#include <string>
#include <string_view>

#include "json.h"
#include "flexbuffer_json.h"

struct diag_func_val;

// temporary shim that pretends to be a JsonObject for the purpose of reusing code between the new
// "math" and the old "arithmetic"/"compare_num"/"u_val"
class kwargs_shim
{
public:
explicit kwargs_shim( std::vector<std::string> const &tokens, char scope );
explicit kwargs_shim( std::vector<diag_func_val> const &tokens, char scope );

std::string get_string( std::string_view key ) const;
double get_float( std::string_view key, double def = 0 ) const;
Expand Down
17 changes: 17 additions & 0 deletions tests/eoc_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ static const effect_on_condition_id
effect_on_condition_EOC_jmath_test( "EOC_jmath_test" );
static const effect_on_condition_id
effect_on_condition_EOC_math_diag_assign( "EOC_math_diag_assign" );
static const effect_on_condition_id
effect_on_condition_EOC_math_diag_w_vars( "EOC_math_diag_w_vars" );
static const effect_on_condition_id effect_on_condition_EOC_math_duration( "EOC_math_duration" );
static const effect_on_condition_id
effect_on_condition_EOC_math_switch_math( "EOC_math_switch_math" );
Expand All @@ -34,6 +36,8 @@ effect_on_condition_EOC_math_var( "EOC_math_var" );
static const effect_on_condition_id
effect_on_condition_EOC_math_weighted_list( "EOC_math_weighted_list" );
static const effect_on_condition_id effect_on_condition_EOC_teleport_test( "EOC_teleport_test" );

static const skill_id skill_survival( "survival" );
namespace
{
void complete_activity( Character &u )
Expand Down Expand Up @@ -143,6 +147,19 @@ TEST_CASE( "EOC_jmath", "[eoc][math_parser]" )
CHECK( std::stod( globvars.get_global_value( "npctalk_var_blorgy" ) ) == Approx( 7 ) );
}

TEST_CASE( "EOC_diag_with_vars", "[eoc][math_parser]" )
{
global_variables &globvars = get_globals();
globvars.clear_global_values();
REQUIRE( globvars.get_global_value( "npctalk_var_myskill_math" ).empty() );
dialogue d( get_talker_for( get_avatar() ), std::make_unique<talker>() );
effect_on_condition_EOC_math_diag_w_vars->activate( d );
CHECK( std::stod( globvars.get_global_value( "npctalk_var_myskill_math" ) ) == Approx( 0 ) );
get_avatar().set_skill_level( skill_survival, 3 );
effect_on_condition_EOC_math_diag_w_vars->activate( d );
CHECK( std::stod( globvars.get_global_value( "npctalk_var_myskill_math" ) ) == Approx( 3 ) );
}

TEST_CASE( "EOC_transform_radius", "[eoc][timed_event]" )
{
// no introspection :(
Expand Down
8 changes: 7 additions & 1 deletion tests/math_parser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "math_parser.h"
#include "math_parser_func.h"

static const skill_id skill_survival( "survival" );
static const spell_id spell_test_spell_pew( "test_spell_pew" );

// NOLINTNEXTLINE(readability-function-cognitive-complexity): false positive
Expand Down Expand Up @@ -177,7 +178,6 @@ TEST_CASE( "math_parser_dialogue_integration", "[math_parser]" )
// reading scoped values with u_val shim
std::string dmsg = capture_debugmsg_during( [&testexp]() {
CHECK_FALSE( testexp.parse( "u_val( 3 )" ) ); // only quoted strings accepted as parameters
CHECK_FALSE( testexp.parse( "u_val( stamina )" ) );
CHECK_FALSE( testexp.parse( "val( 'stamina' )" ) ); // invalid scope for this function
} );
CHECK( testexp.parse( "u_val('stamina')" ) );
Expand All @@ -190,6 +190,12 @@ TEST_CASE( "math_parser_dialogue_integration", "[math_parser]" )
CHECK( testexp.parse( "u_val('time: 1 m')" ) ); // test get_member() in shim
CHECK( testexp.eval( d ) == 60 );

// evaluating string variables in dialogue functions
globvars.set_global_value( "npctalk_var_someskill", "survival" );
CHECK( testexp.parse( "u_skill(someskill)" ) );
get_avatar().set_skill_level( skill_survival, 3 );
CHECK( testexp.eval( d ) == 3 );

// assignment to scoped variables
CHECK( testexp.parse( "u_testvar", true ) );
testexp.assign( d, 159 );
Expand Down

0 comments on commit 168933f

Please sign in to comment.