ELAMPATH=.../path.to/elam LIBS += -L$ELAMPATH -lelam INCLUDEPATH += $ELAMPATH/src CONFIG += link_prlReplace the ELAMPATH variable with the correct path to your installation of ELAM. The link_prl flag will make sure that all required libraries are linked in - the information is stored in the libelam.prl file that is generated when ELAM is compiled.
In the source files that use ELAM, simply add this line:
#include <elam.h>This wrapper include file loads all interfaces defined in ELAM.
1: ELAM::IntEngine engine; 2: ELAM::FloatEngine::configureFloatEngine(engine); 3: engine.setConstant("mypi",QVariant((qreal)3.14159)); 4: QVariant result=engine.evaluate("myvar=2.67*mypi"); 5: qDebug()<<"my result:"<<result.toReal(); 6: qDebug()<<"my variable:"<<engine.getValue("myvar").toReal();
The ELAM::Engine class provides more possibilities, like caching compiled expressions, changing operator precedence, etc. Please see below and the source docu for more details.
ELAM type | QVariant | Description |
any | ELAM::AnyType | cannot be used directly, but tells the engine that any other type (that is registered with QVariant and the engine) is allowed |
exception | ELAM::Exception | thrown when something goes wrong (parser errors, syntactic errors, operational/conversion errors |
int | qlonglong | integer mathematics |
float | qreal/double | floating point mathematics |
bool | bool | boolean mathematics and logic |
string | QString | character strings |
Function | Description |
int(any) | tries to convert the argument to integer |
int + int | adds two integers |
int - int | subtracts two integers |
int * int | multiplies two integers |
int / int | divides two integers |
int % int | calculates the modulo of two integers |
int & int | calculates the bitwis AND of two integers |
int | int | calculates the bitwis OR of two integers |
int ^ int | calculates the bitwis XOR of two integers |
+ int | returns the value as is |
- int | returns the integer negative of the number |
~ int | returns the bitwise negative of the integers |
Integer literals follow the same rules as C-language int literals - they can be expressed as decimals (e.g. 123, -987), octal numbers (starting with "0", e.g. 0123), or hexa-decimal numbers (starting with "0x", e.g. 0x12abCD).
Use the convenience class ELAM::IntEngine or its static method configureIntEngine to get this functionality into an engine.
Function | Description |
float(any) | tries to convert the argument to floating point |
float + float | adds two floating points |
float - float | subtracts two floating points |
float * float | multiplies two floating points |
float / float | divides two floating points |
+ float | returns the value as is |
- float | returns the floating point negative of the number |
Floating point literals are expressed as positive or negative decimal numbers that contain a dot and an optional exponent (e.g. 12.34, 12., .56, 12.0e3, 1e-9). If the literal does not contain a dot or exponent the engine falls back to the integer parser.
Use the convenience class ELAM::FloatEngine or its static method configureFloatEngine to get this functionality into an engine. It is recommended to also load the integer library, even if pure integer calculations are rare in your application.
The boolean library defines some very basic logic operator functionality:
Function | Description |
bool(any) | tries to convert the argument to boolean (see the documentation of QVariant for details) |
bool & bool | results in true if both arguments are true |
bool && bool | results in true if both arguments are true |
bool | bool | results in true if any of the arguments is true |
bool || bool | results in true if any of the arguments is true |
bool ^ bool | results in true if exactly one of the arguments is true and the other false |
bool ^^ bool | results in true if exactly one of the arguments is true and the other false |
! bool | negates the boolean value |
! int | converts the int to bool, then negates the boolean value |
~ bool | negates the boolean value |
Some basic logic functions are also defined:
Function | Description |
if(bool,any[,any]) | tries to interpret the first argument as boolean, if it is true it returns the second argument, if it is false it returns the third or if there is no third argument it returns null Note: all three arguments are executed regardless of which one is returned by the function - this function cannot be used for conditional execution |
isNull(any) | returns true if the argument is null, false otherwise |
isException(any) | returns true if the argument evaluates to an exception (e.g. because a non-existing constant/variable/function is used) |
isExceptionOrNull(any) | returns true if the argument is an exception or null |
catch(any[,any[,any]]) | returns the second argument or true if the first argument evaluates to an exception, returns the third argument or false otherwise - this function is equivalent to isException if it is called with only one argument |
Use the convenience type ELAM::BoolEngine or its methods configureBoolEngine and configureLogicEngine to get this functionality.
Function | Description |
string(any) | tries to convert the argument to string (see the documentation of QVariant for details) |
strlen(string) | returns the length of the string in characters |
concat(...) | takes any number of arguments and returns one string that is the concatenation of all arguments |
string + string | concatenates the two arguments (any of them may also be of a non-string type that is convertible to string) |
String literals start either with single quotes (') or double quotes (") and end with the same type of quote. Special characters can be escaped with backslash (\):
Syntax | Translation after parsing |
\\ | single backslash |
\n | newline |
\r | carriage return |
\t | horizontal tab |
\v | vertical tab |
\b | backspace |
\f | form feed |
\a | alert (BEL) |
\' | single quote |
\" | double quote |
\ooo | up to three octal digits that represent the ASCII value of the desired character |
\xhh | up to two hexadecimal digits that represent the ASCII value of the desired character |
\uhhhh | exactly four hexadecimal digits that represent the 16-bit unicode value of the desired unicode character |
\Uhhhhhhhh | exactly eight hexadecimal digits that represent the 32-bit unicode value of the desired unicode character |
Examples include: "hello World!", 'hello World!', 'line\r\nbreak'.
Use the convenience type ELAM::StringEngine or its static method configureStringEngine to get this functionality.
Function | Description |
typeid(any) | returns the numeric type ID of the argument, see QVariant::type() for details |
typename(any) | returns the name of the type of the argument |
showexception(expression, ...) | takes any number of arguments and returns a string showing either the type of each expression or the details of the exception it threw |
Use the static method ELAM::Engine::configureReflection to get this functionality.
Please read the syntax document for basic concepts of ELAM.
ELAM::IntEngine engine; //set variable: engine.setVariable("myvar",(qlonglong)123); //get variable qDebug()<<engine.getVariable("myvar"); //set constant engine.setConstant("myconst",(qlonglong)123); //remove constant engine.removeConstant("myconst");When setting a variable or constant it should be of a type that is either registered as a primary type or that can be automatically cast into a primary type. Otherwise you will not be able to use the new value with most expressions.
class MyNewType{ public: MyNewType(); MyNewType(int); MyNewType(qreal); ... QString toString()const; }; Q_DECLARE_METATYPE(MyNewType); qRegisterMetaType<MyNewType>();You then need to register the type at the Engine to make sure it is handled as a primary type and not cast into another type that is registered with an auto-cast for this type:
ELAM::Engine engine; engine.registerType(QMetaType::type("MyNewType"));If your type can be cast from other frequently used types it is advisable to register an auto-cast for those types:
//The cast function... static QVariant myNewTypeCast(const QVariant&orig,const Engine&) { //The type of the original and our reaction to it switch(orig.type()){ case QVariant::Int: case QVariant::LongLong: case QVariant::UInt: case QVariant::ULongLong: return MyNewType(orig.toInt()); case QVariant:Double: return MyNewType(orig.toDouble()); //if the type is not known, do not attempt to cast default: return orig; } } ... engine.setAutoCast( //my new target type QMetaType::type("MyNewType"), //the source types it can be cast from QList<int>()<<QVariant::Int<<QVariant::LongLong <<QVariant::UInt<<QVariant::ULongLong <<QVariant::Double, //the function that actually casts myNewTypeCast, //priority at which the cast works in case of conflicts 50 );
Any time that ELAM tries to feed a value into an operator, an assignment, or a function it checks its internal database of registered types and casts. If the type of the value is a primary type then it is left along, regardless of any existing casts. If the type is not primary, then the registered auto casts are searched and the one with the highest priority for this source type is executed. Casts are never chained - each time only one cast is executed, if it does not yield a usable type for the function or operator, then an exception is generated.
The const Engine& parameter to the cast function can be used to retrieve context information about the engine that is calling the cast function - you will normally not need it.
static QVariant myFunction(const QList<QVariant>&args,Engine&) { return args.size(); } ... engine.setFunction("myfunc",myFunction); qDebug()<<engine.evaluate("myfunc(1,2,3,myFunc(4,5,6,7))");The example above implements a function that returns the number of arguments that it is handed by the engine. The expression will then yield the value QVariant(4) - the outer function gets exactly four arguments ("1", "2", "3", and the result of the inner function).
More complex functions have to check for types and the number of arguments, for example converting the type defined above into a string could be done like this:
static QVariant myNewTypeToString(const QList<QVariant>&args,Engine&) { if(args.size()!=1) return ELAM::Exception(ELAM::Exception::ArgumentListError,"Expected one argument."); if(!args[0].canConvert<MyNewType>()) return ELAM::Exception(ELAM::Exception::TypeMismatchError,"Expected argument of MyNewType."); return args[0].value<MyNewType>().toString(); } ... engine.setFunction("mytype2string",myNewTypeToString);
You can use the additional Engine& parameter to retrieve status information about the engine or to change its state (for example if your function is supposed to elevate a variable to a constant). It is not recommended to delete functions, variables, or constants, since this can have effects on the remainder of the expression - expressions are compiles just once (determining which token is a function and which is a variable or constant) and then they are executed, so if a token changes status from function to variable (or vice versa) the effects can be surprising.
A parser function gets the expression that is being parsed as well as some context (engine and start character position) as arguments. If it finds a match for its internal syntax it should return the resulting value and the part of the expression that it accepted, if it does not find a perfect match it should assume that another parser will match and return an empty result.
Let's assume that we allow users to define MyNewType literals as integers in curly braces (e.g. {123}):
QPair<QString,QVariant> myParser(const QString&expr,Engine&engine,int start) { //check start character if(expr[start]!='{')return QPair<QString,QVariant>(); //search for end character QString strval;bool found=false; for(int i=start+1;i<expr.size();i++){ //check for end character if(expr[i]=='}'){ found=true; break; } //copy character strval+=expr[i]; } //if the character was not found: abort if(!found)return QPair<QString,QVariant>(); //convert and check syntax bool ok; int intval=strval.toInt(&ok); if(ok) return QPair<QString,QVariant>( "{"+strval+"}", QVariant::fromValue(MyNewType(intval))); else return QPair<QString,QVariant>(); } //make sure our start character is registered ChracterClassSettings &charclass=engine.characterClasses(); charclass.setLiteralStartClass(charclass.literalStartClass()+"{"); //register the parser engine.setLiteralParser(myParser,"{",50); //test the parser qDebug()<<engine.evaluate("mytype2string({123})");The example function above first searches for matching curly braces, then tries to convert the content to MyNewType and returns the complete literal expression (the loop leaves out the braces, hence they must be re-added) and the resulting value. If it fails at any stage it returns an empty result, so that the engine will continue to search for a matching parser.
The lines below the function make sure the opening curly brace is recognized as a start character by the engine and registers the parser function with that start character at standard priority (50). The last line tests the literal parser and converts the result to a string (using the function from the sub-section above).
The ELAM::UnaryOperator and ELAM::BinaryOperator classes are used internally to track the handlers and types of operators. Instances of those classes are created implicitly when calling the unaryOperator and binaryOperator methods of ELAM::Engine.
static QVariant mySquareOperator(const QVariant&op,Engine&) { qlonglong value=op.toLongLong(); return value*value; } ... ELAM::IntEngine engine; engine.unaryOperator("*").setCallback(mySquareOperator,QVariant::LongLong); qDebug()<<engine.evaluate("2 * *3");The operator handler function is rather simple - it just assumes that it is only called with qlonglong arguments (a fair assumption if it is never registered for another type), and then multiplies that argument with itself. It is then registered for the qlonglong type.
The last line tests the operator - it multiplies 2 with the square of 3 (9), the result should ve a QVariant representing the qlonglong value 18. This also shows that the same name can be used for unary and binary operators - the position relative to the argument(s) decides which one is used: in a series of operators between two expressions the left most is always assumed to be binary and all others are assumed to be unary, if there is no expression to the left then all operators are assumed to be unary.
static QVariant myDivOperator(const QVariant&op1,const QVariant&op2,Engine&) { return op1.toDouble()/op2.toDouble(); } ... ELAM::IntFloatEngine engine; ELAM::BinaryOperator &binop=engine.binaryOperator("//",90); binop.setCallback(myDivOperator,QVariant::LongLong,QVariant::LongLong); binop.setCallback(myDivOperator,QVariant::LongLong,QVariant::Double); binop.setCallback(myDivOperator,QVariant::Double,QVariant::LongLong); binop.setCallback(myDivOperator,QVariant::Double,QVariant::Double); qDebug()<<engine.evaluate("2 // 3");The function above is again trivial - it converts both arguments to qreal and then divides. The function is registered for all combinations of integer and floating point numbers - both qlonglong and qreal can be readily converted to qreal by QVariant.
The priority that is given here registers the operator at the same level as all other multiplicative operators in the default library. Normally if an operator already exists the priority remains unchanged - regardless of whether it matches or not. This behavior can be changed with the optional third argument to this method.
The last line should return a QVariant representing the qreal 0.6666666...
In both cases (unary and binary operators) the Engine& parameter can be used to determine context information from the engine that is calling the operator. It is not recommended (although possible) to change the state of the engine, since this is usually not expected by the user.
For this you need to derive from ELAM::Engine and provide your own methods for accessing variables and constants:
class MyEngine:public ELAM::Engine { QString myvar; public: bool hasVariable(QString name)const { if(name=="myvar")return true; else return ELAM::Engine::hasVariable(name); } QStringList variableNames()const; { QStringList ret=ELAM::Engine::variableNames(); ret.append("myvar"); return ret; } QVariant getVariable(QString name)const { if(name=="myvar")return myvar; else return ELAM::Engine::getVariable(name); } bool setVariable(QString name,QVariant value) { if(name=="myvar"){ myvar=value.toString(); return true; }else return ELAM::Engine::setVariable(name,value); } void removeVariable(QString name) { if(name=="myvar") //ignore the request return; else ELAM::Engine::removeVariable(name); } };For each of variables and constants there are those four methods that are used by other parts of ELAM to access them. If you extend one of the five, you have to adjust all five of them.
Normally you will redirect only the variables/constants that you explicitly know and let the base class handle the remaining ones. The example here deliberately uses a very simplistic approach to show the principle - normally you will have to query the environment of the engine to get better results.
If you override variable handling you need to be aware of the fact that users can assign any value to any variable at run-time. If this is not true for some of your variables you have to do explicit type checking on the value that you are handed.
If you override constant handling you need to be aware that, while users cannot override constants, the default library uses some constants (boolean literals, floating point constants, etc.).
The has* method should check the new environment first and if it does not find anything refer to its base class implementation, it should not return "false" by itself. The *Names function should add the names of variables/constants in its new environment to the list supplied by the base class, so that queries have a complete overview over existing variabes and constants.
The get* method should first inspect its environment for a match - if the match is found it should return the value, otherwise it should refer to the base class. The same is true for the set* method. Additionally the set* method may return false if it finds a type mismatch that it cannot resolve (use QVariants canConvert and value template methods for conversions).
Overriding the remove* method is not necessary if you do not have remove logic for your own variables/constants - the base implementation will ignore remove requests for unknown values.