Using The ELAM Libray

Project File and Includes

Add a few simple lines to your qmake project file:
ELAMPATH=.../path.to/elam
LIBS += -L$ELAMPATH -lelam
INCLUDEPATH += $ELAMPATH/src
CONFIG += link_prl
Replace 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.

Getting Started

You need an instance of ELAM::Engine or one of its child classes as the central instance to execute formulas. The engine is configured with the operations it needs to be able to perform. The engine will then keep track of variables and constants as they are changed by ongoing calculations. The following code represents a very simple example:
 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.

Default Libray

The default library defines functions and operators for integers, floats, booleans and strings. It uses exactly the operator precedence used in the Syntax documentation.

The type "any" is used below to denote any type that is supported by the current engine. Constraints are noted in the description. The following types are used in the default library:
ELAM typeQVariantDescription
anyELAM::AnyTypecannot be used directly, but tells the engine that any other type (that is registered with QVariant and the engine) is allowed
exceptionELAM::Exceptionthrown when something goes wrong (parser errors, syntactic errors, operational/conversion errors
intqlonglonginteger mathematics
floatqreal/doublefloating point mathematics
boolboolboolean mathematics and logic
stringQStringcharacter strings

Optional arguments are marked with [ and ].

Integer Library

The integer library defines some very basic integer functionality:
FunctionDescription
int(any)tries to convert the argument to integer
int + intadds two integers
int - intsubtracts two integers
int * intmultiplies two integers
int / intdivides two integers
int % intcalculates the modulo of two integers
int & intcalculates the bitwis AND of two integers
int | intcalculates the bitwis OR of two integers
int ^ intcalculates the bitwis XOR of two integers
+ intreturns the value as is
- intreturns the integer negative of the number
~ intreturns 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.

Floating Point Library

The floating point library defines some very basic floating point functionality:
FunctionDescription
float(any)tries to convert the argument to floating point
float + floatadds two floating points
float - floatsubtracts two floating points
float * floatmultiplies two floating points
float / floatdivides two floating points
+ floatreturns the value as is
- floatreturns 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.

Boolean Library

This library defines three constants: These constants are meant to be used as literals for boolean values.

The boolean library defines some very basic logic operator functionality:
FunctionDescription
bool(any)tries to convert the argument to boolean (see the documentation of QVariant for details)
bool & boolresults in true if both arguments are true
bool && boolresults in true if both arguments are true
bool | boolresults in true if any of the arguments is true
bool || boolresults in true if any of the arguments is true
bool ^ boolresults in true if exactly one of the arguments is true and the other false
bool ^^ boolresults in true if exactly one of the arguments is true and the other false
! boolnegates the boolean value
! intconverts the int to bool, then negates the boolean value
~ boolnegates the boolean value
In all binary operators above you can substitute one of the boolean arguments with an integer argument: it will first be converted to boolean before the operation is than executed. The &&, ||, and ^^ operators convert any integer arguments to boolean.

Some basic logic functions are also defined:
FunctionDescription
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.

String Library

The string library defines some very basic character string functionality:
FunctionDescription
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 + stringconcatenates 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 (\):
SyntaxTranslation after parsing
\\single backslash
\nnewline
\rcarriage return
\thorizontal tab
\vvertical tab
\bbackspace
\fform feed
\aalert (BEL)
\'single quote
\"double quote
\oooup to three octal digits that represent the ASCII value of the desired character
\xhhup to two hexadecimal digits that represent the ASCII value of the desired character
\uhhhhexactly four hexadecimal digits that represent the 16-bit unicode value of the desired unicode character
\Uhhhhhhhhexactly 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.

Reflection Library

The reflection library defines some type inquiry functions:
FunctionDescription
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.

Extending The ELAM Libray

This section describes how to extend ELAM to suit your own needs above and beyond what the default library provides.

Please read the syntax document for basic concepts of ELAM.

Changing Variables and Constants

The Engine keeps track of constants and variables while it operates. You can query and set them arbitrarily:
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.

Types and Automatic Casts

When you extend the library with new types of values you have to register the new type using the registerType or setAutoCast methods. Since ELAM uses QVariant as its basic type to represent values your new type must be registered with QVariant, e.g.:
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.

Creating new Functions

Functions can execute any operation on any amount of arguments. Only one function with the same name can exist at any time in an engine. The function is handed any arguments that exist when it is called, the implementation of the function has to check itself whether those arguments are correct. A very trivial example is this:
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.

Creating new Literals

With many types it is desireable to have a way of expressing values of that type directly in expressions instead of relying on outside values or conversions from other types. A literal has two registered properties: a parser function and a list of start characters. The engine has a class of literal start characters. When a character is encountered that is part of the literal start character class, then the engine looks for a parser whose configuration contains this character. The matching parsers are tried out in order of descending priority until one of them returns a result.

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).

Creating new Operators

Other than functions operators can be overloaded with different handlers for different argument types. The downside of this is that every combination of primary types that an operator can handle has to be registered at the engine.

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.

Unary Operators

Let's define the unary "*" operator (as the square of its argument) for ELAM::IntEngine, this means we have to get the operator instance for this operator from the engine and add a handler for qlonglong (which is used as generic integer by the library):
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.

Binary Operators

Binary operators are slightly more complex: they have a precedence and two arguments instead of one.
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.

Redirecting Variables and Constants

Sometimes it is desirable to retrieve variables and/or constants from the context the engine is running in instead of copying the values into the engine, executing an expression, then copying them back.

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.