13 Embedded Programming
The following section discusses topics related to using TinyExpr++ in an embedded environment.
Performance
TinyExpr++ is fairly fast compared to compiled C when the expression is short or does hard calculations (e.g., exponentiation). TinyExpr++ is slower compared to C when the expression is long and involves only basic arithmetic.
Here are some example benchmarks:
| Expression | TinyExpr++ | Native C | Comparison |
|---|---|---|---|
| sqrt(a1.5+a2.5) | 1,707 ns | 58.25 ns | 29% slower |
| a+5 | 535 ns | 0.67 ns | 798% slower |
| (1/(a+1)+2/(a+2)+3/(a+3)) | 3,388 ns | 3.941 ns | 859% slower |
Note that TinyExpr++ is slower compared to TinyExpr because of additional type safety checks (e.g., the use of std::variant instead of unions), case insensitivity, and bookkeeping operations.
Refer to compile-time options for flags that can provide optimization.
Device Compatibility
By default, TinyExpr++ uses std::random_device to seed its random number generator, which may cause compatibility issues with some microcontrollers. To instead seed it with the current time, compile with TE_RAND_SEED_TIME.
Volatility
If needing to use a te_parser object as volatile (e.g., accessing it in a system interrupt), then you will need to do the following.
First, declare your te_parser as a non-volatile object outside of the interrupt function (e.g., globally):
te_parser tep;Then, in your interrupt function, create a volatile reference to it:
void OnInterrupt()
{
volatile te_parser& vTep = tep;
}Functions in te_parser which have volatile overloads can then be called directly:
void OnInterrupt()
{
volatile te_parser& vTep = tep;
vTep.set_list_separator(',');
vTep.set_decimal_separator('.');
}The following functions in the te_parser class have volatile overloads:
get_result()success()get_last_error_position()get_decimal_separator()set_decimal_separator()get_list_separator()set_list_separator()
For any other functions, use const_cast<> to remove the parser reference’s volatility:
void OnInterrupt()
{
volatile te_parser& vTep = tep;
// Use 'const_cast<te_parser&>(vTep)' to access
// non-volatile functions.
const_cast<te_parser&>(vTep).set_variables_and_functions(
{ {"STRESS_L", 10.1},
{"P_LEVEL", .5} });
const_cast<te_parser&>(vTep).compile(("STRESS_L*P_LEVEL"));
if (vTep.success())
{
auto res = vTep.get_result();
// Do something else...
}
}Note that it is required to make the initial declaration of your te_parser non-volatile; otherwise, the const_cast<> to the volatile reference will cause undefined behavior.
Floating-point Numbers
double is the default data type used for the parser’s variable types, parameters, and return types. For embedded environments that require float, compile with TE_FLOAT defined to use float instead.
When using this option, it is recommended to use the helper typedef te_type. This will map to either float or double (depending on whether TE_FLOAT is defined). By defining your functions and variables with te_type, you won’t need to replace double and float if needing to change this compiler flag.
For example, a custom function would be written as such:
// Regular function.
te_type my_sum(te_type a, te_type b)
{
return a + b;
}
te_parser tep;
tep.set_variables_and_functions(
{
{ "mysum", my_sum } // function pointer
});
// Lambda.
tep.set_variables_and_functions({
{ "mysum2",
[](te_type a, te_type b) noexcept
{ return a + b; } }
});Exception Handling
TinyExpr++ requires exception handling, although it does attempt to minimize the use of exceptions (e.g., noexcept is used extensively). Syntax errors will be reported without the use of exceptions; issues such as division by zero or arithmetic overflows, however, will internally use exceptions. The parser will trap these exceptions and return NaN (not a number) as the result.
Exceptions can also be thrown when defining custom functions or variables which do not follow the proper naming convention. (Function and variable names may only contain the characters a-z, A-Z, 0-9, ., and _, and must begin with a letter.)
Finally, specifying an illegal character for a list or decimal separator will also throw.
The following functions in te_parser can throw and should be wrapped in try/catch blocks:
compile()evaluate()set_variables_and_functions()add_variable_or_function()set_decimal_separator()set_list_separator()
The caught std::runtime_error exception will provide a description of the error in its what() method.
Virtual Functions
TinyExpr++ does not use virtual functions or derived classes, unless you create a custom class derived from te_expr yourself (refer to “Binding to Custom Classes” for an example). (te_expr defines a virtual destructor that may be implicitly optimized to final if no derived classes are defined.)