Mathematical Expression Parser And JIT Compiler.
- Official Repository (kobalicek/mathpresso)
- [Official Blog (asmbits)] (https://asmbits.blogspot.com/ncr)
- Official Chat (gitter)
- Permissive ZLIB license
MathPresso is a C++ library designed to parse mathematical expressions and compile them into machine code. It's much faster than traditional AST or byte-code based evaluators, because there is basically no overhead in the expression's execution. The JIT compiler is based on AsmJit and works on X86 and X64 architectures.
This is an updated version of MathPresso that uses a stripped-off MPSL engine designed to work with scalar double precision floating points. It has many bugs fixed compared to the last version on google-code and contains improvements that can make execution of certain built-in functions (intrinsics) faster if the host CPU supports SSE4.1 (rounding, fraction, modulo, etc...).
This is also a transitional version that is available to users that want to use MathPresso and cannot wait for the new MPSL engine, which is a work in progress.
- Unary operators:
- Negate
-(x)
- Not
!(x)
- Negate
- Arithmetic operators:
- Assignment
x = y
- Addition
x + y
- Subtraction
x - y
- Multiplication
x * y
- Division
x / y
- Modulo
x % y
- Assignment
- Comparison operators:
- Equal
x == y
- Not equal
x != y
- Greater
x > y
- Greater or equal
x >= y
- Lesser
x < y
- Lesser or equal
x <= y
- Equal
- Functions defined by
addBuiltIns()
:- Check for NaN
isnan(x)
- Check for infinity
isinf(x)
- Check for finite number
isfinite(x)
- Get a sign bit
signbit(x)
- Copy sign
copysign(x, y)
- Round to nearest
round(x)
- Round to even
roundeven(x)
- Truncate
trunc(x)
- Floor
floor(x)
- Ceil
ceil(x)
- Average value
avg(x, y)
- Minimum value
min(x, y)
- Maximum value
max(x, y)
- Absolute value
abs(x)
- Exponential
exp(x)
- Logarithm
log(x)
- Logarithm of base 2
log2(x)
- Logarithm of base 10
log10(x)
- Square root
sqrt(x)
- Fraction
frac(x)
- Reciprocal
recip(x)
- Power
pow(x, y)
- Sine
sin(x)
- Cosine
cos(x)
- Tangent
tan(x)
- Hyperbolic sine
sinh(x)
- Hyperbolic cosine
cosh(x)
- Hyperbolic tangent
tanh(x)
- Arcsine
asin(x)
- Arccosine
acos(x)
- Arctangent
atan(x)
- Arctangent
atan2(x, y)
- Check for NaN
- Constants defined by
addBuiltIns()
:- Infinity
INF
- Not a Number
NaN
- Euler's constant
E = 2.7182818284590452354
- PI
PI = 3.14159265358979323846
- Infinity
MathPresso's expression is always created around a mathpresso::Context
, which defines an environment the expression can access and use. For example if you plan to extend MathPresso with your own function or constant the Context
is the way to go. The Context
also defines inputs and outputs of the expression as shown in the example below:
#include <mathpresso/mathpresso.h>
int main(int argc, char* argv() {
mathpresso::Context ctx;
mathpresso::Expression exp;
// Initialize the context by adding MathPresso built-ins. Without this line
// functions like round(), sin(), etc won't be available.
ctx.addBuiltIns();
// Let the context know the name of the variables we will refer to and
// their positions in the data pointer. We will use an array of 3 doubles,
// so index them by using `sizeof(double)`, like a normal C array.
//
// The `addVariable()` also contains a third parameter that describes
// variable flags, use `kVariableRO` to make a certain variable read-only.
ctx.addVariable("x", 0 * sizeof(double));
ctx.addVariable("y", 1 * sizeof(double));
ctx.addVariable("z", 2 * sizeof(double));
// Compile the expression.
//
// The create parameters are:
// 1. `mathpresso::Context&` - The expression's context / environment.
// 2. `const char* body` - The expression body.
// 3. `unsigned int` - Options, just pass `mathpresso::kNoOptions`.
mathpresso::Error err = exp.compile(ctx, "(x*y) % z", mathpresso::kNoOptions);
// Handle possible syntax or compilation error.
if (err != mathpresso::kErrorOk) {
printf("Expression Error: %u\n", err);
return 1;
}
// To evaluate the expression you need to create the `data` to be passed
// to the expression and initialize it. Every expression returns `double`,
// to return more members simply use the passed `data`.
double data[] = {
1.2, // 'x' - available at data[0]
3.8, // 'y' - available at data[1]
1.3 // 'z' - available at data[2]
};
printf("Output: %f\n", exp.evaluate(data));
return 0;
}
The example above should be self-explanatory. The next example does the same but by using a struct
instead of an array to address the expression's data:
#include <mathpresso/mathpresso.h>
struct Data {
inline Data(double x, double y, double z)
: x(x), y(y), z(z) {}
double x, y, z;
};
int main(int argc, char* argv() {
mathpresso::Context ctx;
mathpresso::Expression exp;
ctx.addBuiltIns();
ctx.addVariable("x", MATHPRESSO_OFFSET(Data, x));
ctx.addVariable("y", MATHPRESSO_OFFSET(Data, y));
ctx.addVariable("z", MATHPRESSO_OFFSET(Data, z));
mathpresso::Error err = exp.compile(ctx, "(x*y) % z", mathpresso::kNoOptions);
if (err != mathpresso::kErrorOk) {
printf("Expression Error: %u\n", err);
return 1;
}
Data data(1.2, 3.8. 1.3);
printf("Output: %f\n", exp.evaluate(&data));
return 0;
}
MathPresso allows to attach an OutputLog
instance to retrieve a human readable error message in case of error. It can output the following:
- Errors, only one as MathPresso stops after the first error
- Warnings
- Abstract syntax tree (AST)
- Assembly (ASM)
Here is the minimum working example that uses OutputLog
to display errors. The interface is very simple, but extensible.
// This is a minimum working example that uses most of MathPresso features. It
// shows how to compile and evaluate expressions and how to handle errors. It
// also shows how to print the generated AST and machine code.
#include <mathpresso/mathpresso.h>
#include <stdio.h>
// The data passed to the expression.
struct Data {
double x, y, z;
};
// By inheriting `OutputLog` one can create a way how to handle possible errors
// and report them to humans. The most interesting and used message type is
// `kMessageError`, because it signalizes an invalid expression. Other message
// types are used mostly for debugging.
struct MyOutputLog : public mathpresso::OutputLog {
MyOutputLog() {}
virtual ~MyOutputLog() {}
virtual void log(unsigned int type, unsigned int line, unsigned int column, const char* message, size_t len) {
switch (type) {
case kMessageError:
printf("[ERROR]: %s (line %u, column %u)\n", message, line, column);
break;
case kMessageWarning:
printf("[WARNING]: %s (line %u, column %u)\n", message, line, column);
break;
case kMessageAstInitial:
printf("[AST-INITIAL]\n%s", message);
break;
case kMessageAstFinal:
printf("[AST-FINAL]\n%s", message);
break;
case kMessageAsm:
printf("[ASSEMBLY]\n%s", message);
break;
default:
printf("[UNKNOWN]\n%s", message);
break;
}
}
};
int main(int argc, char* argv[]) {
MyOutputLog outputLog;
// Create the context, add builtins and define the `Data` layout.
mathpresso::Context ctx;
ctx.addBuiltIns();
ctx.addVariable("x" , MATHPRESSO_OFFSET(Data, x));
ctx.addVariable("y" , MATHPRESSO_OFFSET(Data, y));
ctx.addVariable("z" , MATHPRESSO_OFFSET(Data, z));
// The following options will cause that MathPresso will send everything
// it does to `OutputLog`.
unsigned int options =
mathpresso::kOptionVerbose | // Enable warnings, not just errors.
mathpresso::kOptionDebugAst | // Enable AST dumps.
mathpresso::kOptionDebugAsm ; // Enable ASM dumps.
mathpresso::Expression exp;
mathpresso::Error err = exp.compile(ctx,
"-(-(abs(x * y - floor(x)))) * z * (12.9 - 3)", options, &outputLog);
// Handle possible syntax or compilation error. The OutputLog has already
// received and printed the reason in a human readable form.
if (err) {
printf("ERROR %u\n", err);
return 1;
}
// Evaluate the expression, if compiled.
Data data = { 12.2, 9.2, -1.9 };
double result = exp.evaluate(&data);
printf("RESULT: %f\n", result);
return 0;
}
When executed the output of the application would be something like:
[AST-INITIAL]
* [Binary]
* [Binary]
- [Unary]
- [Unary]
abs [Unary]
- [Binary]
* [Binary]
x
y
floor [Unary]
x
z
- [Binary]
12.900000
3.000000
[AST-FINAL]
* [Binary]
* [Binary]
abs [Unary]
- [Binary]
* [Binary]
x
y
floor [Unary]
x
z
9.900000
[ASSEMBLY]
L0: ; | ..
lea rax, [L2] ; 488D05........ | lea pConst, [L2] ..w
movsd xmm0, [rdx] ; F20F1002 | movsd v3, [pVariables] .r.w
mulsd xmm0, [rdx+8] ; F20F594208 | mulsd v3, [pVariables+8] .r.x
movsd xmm1, [rdx] ; F20F100A | movsd v4, [pVariables] .r..w
roundsd xmm1, xmm1, 9 ; 660F3A0BC909 | roundsd v4, v4, 9 ....x
subsd xmm0, xmm1 ; F20F5CC1 | subsd v3, v4 ...xR
xorpd xmm1, xmm1 ; 660F57C9 | xorpd v5, v5 .... w
subsd xmm1, xmm0 ; F20F5CC8 | subsd v5, v3 ...r x
maxsd xmm1, xmm0 ; F20F5FC8 | maxsd v5, v3 ...R x
mulsd xmm1, [rdx+16] ; F20F594A10 | mulsd v5, [pVariables+16] .R. x
mulsd xmm1, [rax] ; F20F5908 | mulsd v5, [pConst] . R x
movsd [rcx], xmm1 ; F20F1109 | movsd [pResult], v5 R R
L1: ; |
ret ; C3 |
.align 8
L2: ; |
.data CDCCCCCCCCCC2340
RESULT: -1885.514400
- AsmJit - 1.0 or later.
Please consider a donation if you use the project and would like to keep it active in the future.
- Petr Kobalicek kobalicek.petr@gmail.com