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.
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;
.set_list_separator(',');
vTep.set_decimal_separator('.');
vTep}
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.set_variables_and_functions(
tep{
{ "mysum", my_sum } // function pointer
});
// Lambda.
.set_variables_and_functions({
tep{ "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.)