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 12, 2023
1 parent d06ee82 commit cbe3722
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 64 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 @@ -177,6 +177,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
10 changes: 6 additions & 4 deletions doc/NPCs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1428,12 +1428,14 @@ Dialogue functions take single-quoted strings as arguments and can return or man

This section is a work in progress as functions are ported from `arithmetic` to `math`.

_function arguments are either `s`trings or `v`[ariables](#variables)_

| Function | Eval | Assign |Scopes | Description |
|----------|------|--------|-------|-------------|
| game_option ||| N/A<br/>(global) | Return the numerical value of a game option<br/> Example:<br/>`"condition": { "math": [ "game_option('NPC_SPAWNTIME')", ">=", "5"] }`|
| pain ||| u, n | Return or set pain<br/> Example:<br/>`{ "math": [ "n_pain()", "=", "u_pain() + 9000" ] }`|
| skill ||| u, n | Return or set skill level<br/> Example:<br/>`"condition": { "math": [ "u_skill('driving')", ">=", "5"] }`|
| weather ||| N/A<br/>(global) | Return or set a weather aspect<br/><br/>Aspect must be one of:<br/>`temperature` (in Kelvin),<br/>`humidity` (as percentage),<br/>`pressure` (in millibar),<br/>`windpower` (in mph).<br/><br/>Temperature conversion functions are available: `celsius()`, `fahrenheit()`, `from_celsius()`, and `from_fahrenheit()`.<br/><br/>Examples:<br/>`{ "math": [ "weather('temperature')", "<", "from_fahrenheit( 33 )" ] }`<br/>`{ "math": [ "fahrenheit( weather('temperature') )", "==", "21" ] }`|
| game_option('s') ||| N/A<br/>(global) | Return the numerical value of a game option<br/> Example:<br/>`"condition": { "math": [ "game_option('NPC_SPAWNTIME')", ">=", "5"] }`|
| pain() ||| u, n | Return or set pain<br/> Example:<br/>`{ "math": [ "n_pain()", "=", "u_pain() + 9000" ] }`|
| skill(`s`/`v`) ||| u, n | Return or set skill level<br/> Example:<br/>`"condition": { "math": [ "u_skill('driving')", ">=", "5"] }`<br/>`"condition": { "math": [ "u_skill(someskill)", ">=", "5"] }`|
| weather(`s`) ||| N/A<br/>(global) | Return or set a weather aspect<br/><br/>Aspect must be one of:<br/>`temperature` (in Kelvin),<br/>`humidity` (as percentage),<br/>`pressure` (in millibar),<br/>`windpower` (in mph).<br/><br/>Temperature conversion functions are available: `celsius()`, `fahrenheit()`, `from_celsius()`, and `from_fahrenheit()`.<br/><br/>Examples:<br/>`{ "math": [ "weather('temperature')", "<", "from_fahrenheit( 33 )" ] }`<br/>`{ "math": [ "fahrenheit( weather('temperature') )", "==", "21" ] }`|

##### u_val shim
There is a `val()` shim available that can cover the missing arithmetic functions from `u_val` and `npc_val`:
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
44 changes: 27 additions & 17 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> &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
&params, size_t nparams ) const
std::vector<diag_func_val> math_exp::math_exp_impl::_get_diag_vals( std::vector<thingie> &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 & v )
{
vals[i].data.emplace<std::string>( std::move( v ) );
},
[&vals, i]( var & v )
{
vals[i].data.emplace<var_info>( std::move( v.varinfo ) );
},
[this]( auto const &/* 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
63 changes: 47 additions & 16 deletions src/math_parser_diag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <string>
#include <vector>

#include "cata_utility.h"
#include "condition.h"
#include "dialogue.h"
#include "math_parser_shim.h"
Expand All @@ -26,8 +27,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 &
{
throw std::invalid_argument( "Variables are not supported in this context" );
},
},
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 @@ -41,7 +72,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 @@ -53,47 +84,47 @@ std::function<void( dialogue &, double )> u_val_ass( char scope,
}

std::function<double( dialogue & )> option_eval( char /* scope */,
std::vector<std::string> const &params )
std::vector<diag_func_val> const &params )
{
return[option = params[0]]( dialogue const & ) {
return[option = std::string( params[0].str() )]( dialogue const & ) {
return get_option<float>( option );
};
}

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<std::string> const &params )
std::vector<diag_func_val> const &params )
{
return [beta = is_beta( scope ), sid = skill_id( params[0] )]( dialogue const & d ) {
return d.actor( beta )->get_skill_level( sid );
return [beta = is_beta( scope ), sid = params[0] ]( dialogue const & d ) {
return d.actor( beta )->get_skill_level( skill_id( sid.eval( d ) ) );
};
}

std::function<void( dialogue &, double )> skill_ass( char scope,
std::vector<std::string> const &params )
std::vector<diag_func_val> const &params )
{
return [beta = is_beta( scope ), sid = skill_id( params[0] )]( dialogue const & d, double val ) {
return d.actor( beta )->set_skill_level( sid, val );
return [beta = is_beta( scope ), sid = params[0] ]( dialogue const & d, double val ) {
return d.actor( beta )->set_skill_level( skill_id( sid.eval( d ) ), val );
};
}

std::function<double( dialogue & )> weather_eval( char /* scope */,
std::vector<std::string> const &params )
std::vector<diag_func_val> const &params )
{
if( params[0] == "temperature" ) {
return []( dialogue const & ) {
Expand All @@ -115,11 +146,11 @@ std::function<double( dialogue & )> weather_eval( char /* scope */,
return get_weather().weather_precise->pressure;
};
}
throw std::invalid_argument( "Unknown weather aspect " + params[0] );
throw std::invalid_argument( "Unknown weather aspect " + std::string( params[0].str() ) );
}

std::function<void( dialogue &, double )> weather_ass( char /* scope */,
std::vector<std::string> const &params )
std::vector<diag_func_val> const &params )
{
if( params[0] == "temperature" ) {
return []( dialogue const &, double val ) {
Expand All @@ -146,5 +177,5 @@ std::function<void( dialogue &, double )> weather_ass( char /* scope */,
get_weather().clear_temp_cache();
};
}
throw std::invalid_argument( "Unknown weather aspect " + params[0] );
throw std::invalid_argument( "Unknown weather aspect " + std::string( params[0].str() ) );
}
38 changes: 27 additions & 11 deletions src/math_parser_diag.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
#include <functional>
#include <string>
#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 @@ -16,9 +19,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 @@ -28,7 +44,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 @@ -40,28 +56,28 @@ 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 & )> option_eval( 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<std::string> const &params );
std::vector<diag_func_val> const &params );
std::function<void( dialogue &, double )> skill_ass( char scope,
std::vector<std::string> const &params );
std::vector<diag_func_val> const &params );

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

inline std::array<dialogue_func_eval, 5> const dialogue_eval_f{
dialogue_func_eval{ "val", "un", -1, u_val },
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
Loading

0 comments on commit cbe3722

Please sign in to comment.