8 Custom Extensions
Binding to Custom Variables
Along with the built-in functions and constants, you can create custom variables for use in expressions:
#include "tinyexpr.h"
#include <iostream>
#include <iomanip>
int main(int argc, char* argv[])
{
if (argc < 2)
{
std::cout << "Usage: example \"expression\"\n";
return EXIT_SUCCESS;
}
const char* expression = argv[1];
te_type x{ 0 }, y{ 0 }; // x and y are bound at eval-time.
// Store variable names and pointers.
;
te_parser tep.set_variables_and_functions({ {"x", &x}, {"y", &y} });
tep
if (tep.compile(expression)) // Compile the expression and check for errors.
{
/* The variables can be changed here, and evaluate can be called multiple
times. This is efficient because the parsing has already been done.*/
= 3; y = 4;
x const auto r = tep.evaluate();
std::cout << "Result:\n\t" << r << "\n";
}
else // Show the user where the error is at.
{
std::cout << "\t " << std::setfill(' ') <<
std::setw(tep.get_last_error_position()) << '^' << "\tError here\n";
}
return EXIT_SUCCESS;
}
Binding to Custom Functions
TinyExpr++ can call custom functions also. Here is a short example:
te_type my_sum(te_type a, te_type b)
{
/* Example function that adds two numbers together. */
return a + b;
}
;
te_parser tep.set_variables_and_functions(
tep{
{ "mysum", my_sum } // function pointer
});
const auto r = tep.evaluate("mysum(5, 6)");
// will be 11
Here is an example of using a lambda:
;
te_parser tep.set_variables_and_functions({
tep{ "mysum",
[](te_type a, te_type b) noexcept
{ return a + b; } }
});
const auto r = tep.evaluate("mysum(5, 6)");
// will be 11
Binding to Custom Classes
A class derived from te_expr
can be bound to custom functions. This enables you to have full access to an object (via these functions) when parsing an expression.
The following demonstrates creating a te_expr
-derived class which contains an array of values:
class te_expr_array : public te_expr
{
public:
explicit te_expr_array(const te_variable_flags type) noexcept :
(type) {}
te_exprstd::array<te_type, 5> m_data = { 5, 6, 7, 8, 9 };
};
Next, create two functions that can accept this object and perform actions on it. (Note that proper error handling is not included for brevity.):
// Returns the value of a cell from the object's data.
te_type cell(const te_expr* context, te_type a)
{
auto* c = dynamic_cast<const te_expr_array*>(context);
return static_cast<te_type>(c->m_data[static_cast<size_t>(a)]);
}
// Returns the max value of the object's data.
te_type cell_max(const te_expr* context)
{
auto* c = dynamic_cast<const te_expr_array*>(context);
return static_cast<te_type>(
*std::max_element(c->m_data.cbegin(), c->m_data.cend()));
}
Finally, create an instance of the class and connect the custom functions to it, while also adding them to the parser:
{ TE_DEFAULT };
te_expr_array teArray
;
te_parser tep.set_variables_and_functions(
tep{
{"cell", cell, TE_DEFAULT, &teArray},
{"cellmax", cell_max, TE_DEFAULT, &teArray}
});
// change the object's data and evaluate their summation
// (will be 30)
.m_data = { 6, 7, 8, 5, 4 };
teArrayauto result = tep.evaluate("SUM(CELL 0, CELL 1, CELL 2, CELL 3, CELL 4)");
// call the other function, getting the object's max value
// (will be 8)
= tep.evaluate("CellMax()"); res
Valid variable and function names consist of a letter or underscore followed by any combination of: letters a–z
or A–Z
, digits 0–9
, periods, and underscores.