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;
tep.set_variables_and_functions({ {"x", &x}, {"y", &y} });
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.*/
x = 3; y = 4;
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 also call custom functions. 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;
tep.set_variables_and_functions(
{
{ "mysum", my_sum } // function pointer
});
const auto r = tep.evaluate("mysum(5, 6)");
// will be 11Here is an example of using a lambda:
te_parser tep;
tep.set_variables_and_functions({
{ "mysum",
[](te_type a, te_type b) noexcept
{ return a + b; } }
});
const auto r = tep.evaluate("mysum(5, 6)");
// will be 11Binding to Functions Accepting Strings
Functions can also accept quoted string literals from a formula. For example:
DBQUERY("/Equipment/Temp", 3)Such a function takes a std::span of te_arg, where each te_arg is either a number or a string:
using te_arg = std::variant<te_type, std::string_view>;
using te_arg_fun = te_type (*)(std::span<const te_arg>);
using te_arg_confun = te_type (*)(const te_expr*, std::span<const te_arg>);Any argument can be a string or a number, in any position, and there is no limit on how many are passed. Because of this, the parser does not verify the argument count the way it does for te_fun0–te_fun24. Review args.size() and each argument’s type yourself, returning te_parser::te_nan if the call is not valid:
te_type db_query(std::span<const te_arg> args)
{
if (args.size() != 2 ||
!std::holds_alternative<std::string_view>(args[0]) ||
!std::holds_alternative<te_type>(args[1]))
{ return te_parser::te_nan; }
// 'lookup' could be a database query or such
return lookup(std::get<std::string_view>(args[0]),
std::get<te_type>(args[1]));
}
te_parser tep;
tep.set_variables_and_functions(
{
{ "dbquery", static_cast<te_arg_fun>(db_query) }
});
const auto r = tep.evaluate(R"(DBQUERY("/Equipment/Temp", 3))");Note the cast to te_arg_fun; this tells the compiler which of the function types to bind to.
A te_arg_confun receives a client object as its first argument, exactly like the te_confun0–te_confun24 functions described in Binding to Custom Classes:
class te_database : public te_expr
{
public:
explicit te_database(const te_variable_flags type) noexcept :
te_expr(type) {}
std::map<std::string, te_type, std::less<>> m_rows =
{ { "voltage", 240 }, { "current", 13 } };
};
te_type query_db(const te_expr* context, std::span<const te_arg> args)
{
auto* db = dynamic_cast<const te_database*>(context);
if (db == nullptr || args.size() != 1 ||
!std::holds_alternative<std::string_view>(args[0]))
{ return te_parser::te_nan; }
const auto found = db->m_rows.find(std::get<std::string_view>(args[0]));
return (found == db->m_rows.cend()) ? te_parser::te_nan : found->second;
}
te_database db{ TE_DEFAULT };
te_parser tep;
tep.set_variables_and_functions(
{
{ "query", static_cast<te_arg_confun>(query_db), TE_DEFAULT, &db }
});
// will be 3120
const auto r = tep.evaluate(R"(QUERY("voltage") * QUERY("current"))");A string literal is opened and closed by a double quote. There are no escape sequences, so a literal cannot itself contain a double quote.
String literals are only valid as arguments to these functions. Using one anywhere else (sin("abc"), 1 + "abc", or "abc" on its own) is a syntax error.
A std::string_view argument points into the parser’s copy of the expression, so it remains valid for the lifetime of the compiled expression. The std::span, however, is only valid for the duration of the call; copy anything you need to keep.
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 :
te_expr(type) {}
std::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_expr_array teArray{ TE_DEFAULT };
te_parser tep;
tep.set_variables_and_functions(
{
{"cell", cell, TE_DEFAULT, &teArray},
{"cellmax", cell_max, TE_DEFAULT, &teArray}
});
// change the object's data and evaluate their summation
// (will be 30)
teArray.m_data = { 6, 7, 8, 5, 4 };
auto 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)
result = tep.evaluate("CellMax()");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.