From 3658b487f89cbfe9e4270bf02c3bea47114561fb Mon Sep 17 00:00:00 2001 From: konrad Date: Sat, 25 Dec 2010 17:36:23 +0000 Subject: [PATCH] finish docu git-svn-id: https://silmor.de/svn/softmagic/elam/trunk@681 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33 --- doc/library.html | 335 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- doc/syntax.html | 28 ++++- src/elamengine.h | 5 +- 3 files changed, 351 insertions(+), 17 deletions(-) diff --git a/doc/library.html b/doc/library.html index c9bb4cc..6cd4f2a 100644 --- a/doc/library.html +++ b/doc/library.html @@ -1,11 +1,51 @@ -ELAM +ELAM Library +

Using The ELAM Libray

Project File and Includes

-

Calling It

+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

@@ -15,7 +55,7 @@ The type "any" is used below to denote any type that is supported by the current - + @@ -44,7 +84,11 @@ The integer library defines some very basic integer functionality: -
ELAM typeQVariantDescription
anyELAM::AnyTypecannot be used directly, but tells the engine that any type is allowed
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
+ 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

@@ -58,18 +102,293 @@ The floating point library defines some very basic floating point functionality: 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. -

Basic Types, Operators, Functions and Variables

Extending The ELAM Libray

-

Redirecting Variables and Constants

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

-

Creating new Operators

+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)
+{
+  //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.

Creating new Functions

-

Creating new Types

+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)
+{
+  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)
+{
+  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);
+
+ +

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)
+{
+  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)
+{
+  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... \ No newline at end of file diff --git a/doc/syntax.html b/doc/syntax.html index 2411963..d34bcd5 100644 --- a/doc/syntax.html +++ b/doc/syntax.html @@ -64,20 +64,18 @@ Constants are variables that have been marked as immutable, they can be of any t There are two types of operators: unary and binary.

-Unary operator precede an expression and change it, returning the result. For example the unary "-" operator in the default library negates its numeric argument (e.g. if "a" is 12, then "-a" returns -12).

+Unary operators precede an expression and change it, returning the result. For example the unary "-" operator in the default library negates its numeric argument (e.g. if "a" is 12, then "-a" returns -12).

Binary operators operate on two expressions, one to the left and one to the right of the operator.

-If operators follow each other (e.g. "a +| -b") then the operators must be separated by spaces.

+If operators follow each other (e.g. "a != -b") then the operators must be separated by spaces.

Parentheses group operations and may alter the order in which they are executed. Parentheses are evaluated before any other operation.

-The equals sign ("=") has a special meaning when used in operators. In the normal mode the direct assignment operator "=" cannot be overloaded with a user specific function. If an operator ends in "=" and there is no overloaded operator, then the remainder is interpreted as a binary operator and the result assigned to the left side of the operator, for example "a+=b" is interpreted as a short form of "a=a+b". -

Operator Precedence

-Operators are interpreted in a specific order of precedence. First the highest precedence operators are evaluated, then the next lower precedence level, etc. with assignments being executed last. Consecutive operations with the same precedence level are executed from left to right.

+Operators are interpreted in a specific order of precedence. First the highest precedence operators are evaluated, then the next lower precedence level, etc. with assignments being executed last. Consecutive binary operations with the same precedence level are executed from left to right. Consecutive unary operations are executed from right (nearest to the argument of the operator) to left (farthest to the argument).

The default library has the following precedence order: @@ -107,8 +105,24 @@ This means for example that in "a=9+1+2*(3-1)" these operations are executed in

  • "9+1" yields "10"
  • "10+4" yields "14"
  • the variable "a" is assigned the value "14" +
  • the expression returns the result of the last operation (the assignment) to the calling context +

    Parentheses

    + +Parentheses are used to change the order in which operations are executed by grouping part of the expression into a unit that is calculated before the remaining operation surrounding it is executed. Inside the parentheses the normal precedence order applies again.

    + +For example: in "2*(3-1*5)/4" the expression "3-1*5" inside the parentheses is resolved before the remaining multiplication and division around it are executed. While calculating the content of the parentheses the normal precedence applies, so the multiplication is executed before the subtraction. + +

    Assignments

    + +The equals sign ("=") has a special meaning when used in operators. In the normal mode the direct assignment operator "=" cannot be overloaded with a user specific function. If an operator ends in "=" and there is no overloaded operator, then the remainder is interpreted as a binary operator and the result assigned to the left side of the operator, for example "a+=b" is interpreted as a short form of "a=a+b".

    + +In other words: if ELAM encounters an operator that equals its notion of the assignment operator (normally "="), then it is always interpreted as assignment - even if another operator with the same name exists. If ELAM encounters an operator that contains the assignment syntax (normally: one that ends in "="), then it first checks whether there is a more specific operator (above, e.g. "==") and uses it if found, if there is none, then it checks whether this could be a compound operation (e.g. "+=", which first adds and then assigns the result back to the left argument).

    + +Assignment syntax can be changed by changing the character class for the assignment operator. + +

    Functions

    Functions take values of any kind as arguments and return exactly one value. Function names follow the same syntactic rules as variable names. Functions and variables share their namespace - no variable can have the same name as a function. Function arguments are separated by commas.

    @@ -118,7 +132,7 @@ The default library knows the following functions: FunctionDescription int(value)converts value to integer, rounding towards zero float(value)converts value to a floating point number -str(value)converts value to string +string(value)converts value to string concat(v1,v2,...)concatenates strings @@ -140,7 +154,7 @@ The "special" characters are those with a specific built-in meaning to the parse - +
    CharactersDescriptionRules
    Parentheses "(", ")"change the order of evaluation by grouping the inner operations to be performed before the outer operations, or by enclosing the arguments of a functionthe two parentheses characters must differ and must not match any other class
    Comma ","separated the arguments of a functionsingle character that must not be contained in any other class
    Comma ","separates the arguments of a functionsingle character that must not be contained in any other class
    Equals "="denotes assignments, the character itself cannot be overridden as operator, combinations with other operators can be overriddenone or two characters: prefix and postfix, they must differ, they both must be part of the operator class

    diff --git a/src/elamengine.h b/src/elamengine.h index 14e595d..b7f08ca 100644 --- a/src/elamengine.h +++ b/src/elamengine.h @@ -147,9 +147,10 @@ class Engine:public QObject ///deletes a variable or constant void removeValue(QString); - /**sets the function + /**Sets a new function for a specific function name. + If there already is a function of that name it is overridden. \returns true on success or false if: - - the name is not valid + - the name is not syntactically valid - the function is null - a constant or variable of the same name exists already*/ bool setFunction(QString,Function); -- 1.7.2.5