tokenizer, test of tokenizer
authorkonrad <konrad@6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33>
Sun, 14 Nov 2010 21:39:24 +0000 (21:39 +0000)
committerkonrad <konrad@6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33>
Sun, 14 Nov 2010 21:39:24 +0000 (21:39 +0000)
git-svn-id: https://silmor.de/svn/softmagic/elam/trunk@627 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33

13 files changed:
src/Doxyfile
src/elam.h
src/elam.pro
src/elamengine.cpp
src/elamengine.h
src/elamexpression.cpp
src/elamexpression.h
src/elamintengine.cpp
src/elamvalue.cpp
src/elamvalue.h
tests/parser/parser.cpp [new file with mode: 0644]
tests/parser/parser.h [new file with mode: 0644]
tests/parser/parser.pro [new file with mode: 0644]

index af2e34b..68a7a23 100644 (file)
@@ -308,7 +308,7 @@ EXTRACT_PRIVATE        = NO
 # If the EXTRACT_STATIC tag is set to YES all static members of a file
 # will be included in the documentation.
 
-EXTRACT_STATIC         = YES
+EXTRACT_STATIC         = NO
 
 # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
 # defined locally in source files will be included in the documentation.
@@ -598,7 +598,7 @@ INPUT_ENCODING         = UTF-8
 # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
 # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
 
-FILE_PATTERNS          =
+FILE_PATTERNS          = *.h
 
 # The RECURSIVE tag can be used to turn specify whether or not subdirectories
 # should be searched for input files as well. Possible values are YES and NO.
@@ -742,7 +742,7 @@ USE_HTAGS              = NO
 # will generate a verbatim copy of the header file for each class for
 # which an include is specified. Set to NO to disable this.
 
-VERBATIM_HEADERS       = YES
+VERBATIM_HEADERS       = NO
 
 #---------------------------------------------------------------------------
 # configuration options related to the alphabetical class index
@@ -1460,7 +1460,7 @@ HIDE_UNDOC_RELATIONS   = YES
 # toolkit from AT&T and Lucent Bell Labs. The other options in this section
 # have no effect if this option is set to NO (the default)
 
-HAVE_DOT               = NO
+HAVE_DOT               = YES
 
 # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
 # allowed to run in parallel. When set to 0 (the default) doxygen will
index 8df3bdf..e62e380 100644 (file)
@@ -9,4 +9,12 @@
 #include "elamengine.h"
 #include "elamintengine.h"
 
+/** \mainpage ELAM - Elementary Logic and Arithmetic Machine
+
+Please see the <a href="../index.html">main docu</a> for build instructions and a tutorial on how to use ELAM.<p>
+
+See the ELAM namespace for member documentation.
+
+*/
+
 #endif
index 2082bfa..c3b2961 100644 (file)
@@ -19,7 +19,8 @@ HEADERS += \
 SOURCES += \
        elamvalue.cpp \
        elamengine.cpp \
-       elamintengine.cpp
+       elamintengine.cpp \
+       elamexpression.cpp
 
 INCLUDEPATH += .
 DEPENDPATH += .
index 1ec523c..d622a3f 100644 (file)
@@ -6,6 +6,8 @@
 #include "elamengine.h"
 #include "elamvalue.h"
 
+#include <QDebug>
+
 namespace ELAM {
 ///////////////////////////////////////////////////////////////////////////////
 // Engine
@@ -14,6 +16,18 @@ class DPTR_CLASS_NAME(Engine):public DPtr
 {
        public:
                CharacterClassSettings cclass;
+               QMap<QString,QVariant> vars,consts;
+               QMap<QString,Function> funcs;
+               struct LiteralParser_s
+               {
+                       LiteralParser parser;
+                       QString start;
+                       int prio;
+                       //this operator is reversed, so that highest prio is in element 0 after qSort
+                       bool operator<(const LiteralParser_s&l2)const
+                       {return prio>l2.prio;}
+               };
+               QList<LiteralParser_s>parsers;
 };
 DEFINE_DPTR(Engine);
 
@@ -47,31 +61,73 @@ Expression Engine::expression(QList< Token > )
 
 QList< Token > Engine::tokenize(QString ex)
 {
+       //presets
        Token::Type ctype=Token::Invalid;
        QString ctok;
        QList<Token> ret;
        int cline=1,ccol=0;
        int sline=cline,scol=ccol;
+       int litend=-1;
+       //go through expression
        for(int i=0;i<ex.size();i++){
                if(ex[i]=='\n'){cline++;ccol=0;}
                ccol++;
-               //if we have a type: check that we are still in it
+               //skip everything till end of a literal
+               if(litend>i)continue;
+               //get type of current character
                Token::Type ntype=d->cclass.charType(ex[i],ctype);
                //check for invalid stuff
                if(ntype==Token::Invalid)
-                       return QList<Token>()<<Token(Position(sline,scol));
+                       return QList<Token>()<<Token(Position(cline,ccol));
                //check whether we are still inside the same token
                if(ntype==ctype)
                        ctok+=ex[i];
                else{
-                       if(ctype!=Token::Invalid)
+                       //store old token
+                       if(ctype!=Token::Invalid && ctype!=Token::Whitespace && ctok!="")
                                ret<<Token(ctok,ctype,Position(sline,scol));
-                       ctype=ntype;
+                       //reset state
                        ctok.clear();
                        sline=cline;
                        scol=ccol;
+                       //handle current char
+                       switch(ntype){
+                               case Token::ParOpen:
+                               case Token::ParClose:
+                               case Token::Comma:
+                                       //special chars
+                                       ret<<Token(QString(ex[i]),ntype,Position(cline,ccol));
+                                       ctype=Token::Invalid;
+                                       break;
+                               case Token::Whitespace:
+                               case Token::Operator:
+                               case Token::Name:
+                                       //start new token
+                                       ctype=ntype;
+                                       ctok+=ex[i];
+                                       break;
+                               case Token::Literal:{
+                                       //parse it
+                                       QPair<QString,QVariant>lt=parseLiteral(ex,i);
+                                       //check for failure
+                                       if(lt.first=="")
+                                               return QList<Token>()<<Token(Position(cline,ccol));
+                                       //store token
+                                       ret<<Token(lt.first,lt.second,Position(cline,ccol));
+                                       //make the loop skip the rest
+                                       litend=i+lt.first.size();
+                                       ctype=Token::Invalid;
+                                       break;}
+                               default://nothing
+                                       qDebug()<<"oops. unexpected token type at line"<<cline<<"col"<<ccol;
+                                       ctype=Token::Invalid;
+                                       break;
+                       }
                }
        }
+       //add remaining stuff
+       if(ctype!=Token::Invalid && ctype!=Token::Whitespace && ctok!="")
+               ret<<Token(ctok,ctype,Position(sline,scol));
        return ret;
 }
 
@@ -80,6 +136,144 @@ CharacterClassSettings Engine::characterClasses()
        return d->cclass;
 }
 
+QVariant Engine::getConstant(QString n) const
+{
+       if(d->consts.contains(n))return d->consts[n];
+       return QVariant();
+}
+
+QVariant Engine::getVariable(QString n) const
+{
+       if(d->vars.contains(n))return d->vars[n];
+       return QVariant();
+}
+
+QVariant Engine::getValue(QString n) const
+{
+       if(d->consts.contains(n))return d->consts[n];
+       if(d->vars.contains(n))return d->vars[n];
+       return QVariant();
+}
+
+bool Engine::hasConstant(QString n) const
+{
+       return d->consts.contains(n);
+}
+
+bool Engine::hasVariable(QString n) const
+{
+       return d->vars.contains(n);
+}
+
+bool Engine::hasValue(QString n) const
+{
+       return d->vars.contains(n) || d->consts.contains(n);
+}
+
+void Engine::removeConstant(QString n)
+{
+       d->consts.remove(n);
+}
+
+void Engine::removeVariable(QString n)
+{
+       d->vars.remove(n);
+}
+
+void Engine::removeValue(QString n)
+{
+       d->consts.remove(n);
+       d->vars.remove(n);
+}
+
+bool Engine::setConstant(QString n, QVariant v)
+{
+       if(!d->cclass.isName(n))return false;
+       if(d->funcs.contains(n))return false;
+       d->vars.remove(n);
+       d->consts.insert(n,v);
+       return true;
+}
+
+bool Engine::setVariable(QString n, QVariant v)
+{
+       if(!d->cclass.isName(n))return false;
+       if(d->consts.contains(n))return false;
+       if(d->funcs.contains(n))return false;
+       d->vars.insert(n,v);
+       return true;
+}
+
+bool Engine::hasFunction(QString n) const
+{
+       return d->funcs.contains(n);
+}
+
+Function Engine::getFunction(QString n) const
+{
+       if(d->funcs.contains(n))return d->funcs[n];
+       return 0;
+}
+
+bool Engine::setFunction(QString n, ELAM::Function p)
+{
+       if(p==0)return false;
+       if(!d->cclass.isName(n))return false;
+       if(d->consts.contains(n))return false;
+       if(d->vars.contains(n))return false;
+       d->funcs.insert(n,p);
+       return true;
+}
+
+void Engine::removeFunction(QString n)
+{
+       d->funcs.remove(n);
+}
+
+bool Engine::setLiteralParser ( ELAM::LiteralParser parser, QString startchars, int prio )
+{
+       if(parser==0 || startchars=="" || prio<0 || prio>100)
+               return false;
+       for(int i=0;i<d->parsers.size();i++){
+               if(d->parsers[i].parser==parser){
+                       d->parsers[i].start=startchars;
+                       d->parsers[i].prio=prio;
+                       return true;
+               }
+       }
+       Private::LiteralParser_s s;
+       s.parser=parser;
+       s.prio=prio;
+       s.start=startchars;
+       d->parsers<<s;
+       return true;
+}
+
+void Engine::removeLiteralParser ( ELAM::LiteralParser parser )
+{
+       for(int i=0;i<d->parsers.size();i++)
+               if(d->parsers[i].parser==parser){
+                       d->parsers.removeAt(i);
+                       return;
+               }
+}
+
+QPair< QString, QVariant > Engine::parseLiteral ( QString ex, int start)
+{
+       QChar sc=ex[start];
+       //find any parser that matches the start char
+       QList<Private::LiteralParser_s>cand;
+       for(int i=0;i<d->parsers.size();i++)
+               if(d->parsers[i].start.contains(sc))
+                       cand<<d->parsers[i];
+       if(cand.size()<1)
+               return QPair<QString,QVariant>();
+       //sort them
+       qSort(cand);
+       //execute
+       return cand[0].parser(ex,*this,start);
+}
+
 
 /////////////////////////////////////////////////////////////////
 // character classes
@@ -214,6 +408,9 @@ bool CharacterClassSettings::isConsistent() const
                return false;
        if(d->assignmentChars.second!=0 && !d->operatorClass.contains(d->assignmentChars.second))
                return false;
+       //check parentheses are different
+       if(d->parenthesesChars.first==d->parenthesesChars.second)
+               return false;
        //check remaining special chars are not in any other class
        any+=d->operatorClass;
        if(any.contains(d->parenthesesChars.first) ||
@@ -224,6 +421,78 @@ bool CharacterClassSettings::isConsistent() const
        return true;
 }
 
+Token::Type CharacterClassSettings::charType(QChar c, ELAM::Token::Type otype) const
+{
+       //special char?
+       if(c==d->parenthesesChars.first)return Token::ParOpen;
+       if(c==d->parenthesesChars.second)return Token::ParClose;
+       if(c==d->commaChar)return Token::Comma;
+       //is it a name?
+       if(otype==Token::Name){
+               //is this a continuation
+               if(d->nameClass.second.contains(c))return Token::Name;
+       }else{
+               //is this the start of a name
+               if(d->nameClass.first.contains(c))return Token::Name;
+       }
+       //is it the start of a literal?
+       if(d->literalClass.contains(c))return Token::Literal;
+       //operator?
+       if(d->operatorClass.contains(c))return Token::Operator;
+       //whitespace?
+       if(d->whitespaceClass.contains(c))return Token::Whitespace;
+       //must be invalid noise
+       return Token::Invalid;
+}
+
+bool CharacterClassSettings::isAssignment(QString op)const
+{
+       //sanity checks: size
+       if(op.size()<1)return false;
+       if(d->assignmentChars.first!=0 && d->assignmentChars.second!=0)
+               if(op.size()<2)return false;
+       //check we have assignments at all
+       if(d->assignmentChars.first==0 && d->assignmentChars.second==0)return false;
+       //check it is assignment
+       if(d->assignmentChars.first!=0 && op[0]!=d->assignmentChars.first)
+               return false;
+       if(d->assignmentChars.second!=0 && op[op.size()-1]!=d->assignmentChars.second)
+               return false;
+       //check it is an operator
+       for(int i=0;i<op.size();i++)
+               if(!d->operatorClass.contains(op[i]))
+                       return false;
+       //passed everything
+       return true;
+}
+
+bool CharacterClassSettings::isSimpleAssignment ( QString op) const
+{
+       if(op.size()<1)return false;
+       QString c;
+       if(d->assignmentChars.first!=0)c+=d->assignmentChars.first;
+       if(d->assignmentChars.second!=0)c+=d->assignmentChars.second;
+       return op==c;
+}
+
+
+bool CharacterClassSettings::isName(QString n) const
+{
+       if(n.size()<1)return false;
+       if(!d->nameClass.first.contains(n[0]))return false;
+       for(int i=0;i<n.size();i++)
+               if(!d->nameClass.second.contains(n[i]))return false;
+       return true;
+}
+
+bool CharacterClassSettings::isOperator(QString op) const
+{
+       for(int i=0;i<op.size();i++)
+               if(!d->operatorClass.contains(op[i]))
+                       return false;
+       return true;
+}
+
 
 /////////////////////////////////////////////////////////////////
 // Unary Operator
@@ -241,6 +510,13 @@ UnaryOperator::UnaryOperator(const UnaryOperator& op)
 {
 }
 
+UnaryOperator& UnaryOperator::UnaryOperator::operator=(const ELAM::UnaryOperator& op)
+{
+       d=op.d;
+       return *this;
+}
+
+
 UnaryOperator::UnaryOperator()
 {
 }
index 872114f..2c58557 100644 (file)
@@ -3,8 +3,8 @@
 // (c) Konrad Rosenbaum, 2010
 // protected under the GNU LGPL v3 or at your option any newer
 
-#ifndef _ENGINE_H
-#define _ENGINE_H
+#ifndef ELAM_ENGINE_H
+#define ELAM_ENGINE_H
 
 #include <QStringList>
 #include <QObject>
 
 namespace ELAM {
 
-/**pointer to a function wrapping an unary operator
+/** \brief pointer to a function wrapping an unary operator
 \param op the operand to be worked on
 \returns the result of the operation*/
 typedef QVariant (*UnaryOperatorCall)(const QVariant&op);
 
+/** \brief Wraps a particular unary operator.
+
+You can use the methods of this class to change the routines that handle the operator and the types on which it operates. Instances of this class are implicitly shared - meaning calls on a copy of an instance are also visible on the original and all other copies.
+*/
 class UnaryOperator
 {
        DECLARE_SHARED_DPTR(d);
        public:
+               /**copy constructor,
+               the instance will access the exact same operator as the original, any setting that is done in the copy is also done in the original and all other copies.*/
                UnaryOperator(const UnaryOperator&);
+               ///instantiates an empty operator
                UnaryOperator();
+               ///deletes and operator
                ~UnaryOperator();
                
+               /**the operator becomes a shared copy of op and abandones its old link*/
+               UnaryOperator& operator=(const UnaryOperator&op);
+               
                /**sets a callback function for the operator and a specific typ
                \param callback the function to call, if it is null the type is deleted from this operators type list
                \param type the type of variable to work on, this must be a type registered with QVariant, if this type is already known to the operator its callback is replaced
@@ -60,12 +71,16 @@ class UnaryOperator
                QVariant execute(const QVariant&)const;
 };
 
-/**pointer to a function wrapping a binary operator
+/** \brief pointer to a function wrapping a binary operator
 \param op1 the left operand
 \param op2 the right operand
 \returns the result of the operation*/
 typedef QVariant (*BinaryOperatorCall)(const QVariant&op1,const QVariant&op2);
 
+/** \brief Wraps a particular binary operator.
+
+You can use the methods of this class to change the routines that handle the operator and the types on which it operates. Instances of this class are implicitly shared - meaning calls on a copy of an instance are also visible on the original and all other copies.
+*/
 class BinaryOperator
 {
        DECLARE_SHARED_DPTR(d);
@@ -77,37 +92,157 @@ class BinaryOperator
 
 /**pointer to a function wrapping a mathematical function
 \param args the list of arguments
-\returns the result of the function*/
+\returns the result of the function or ELAM::Exception in case or errors
+
+Functions must check their arguments for validity before they start calculating. On error a function should returns an ELAM::Exception.
+*/
 typedef QVariant (*Function)(const QList<QVariant>&args);
 
+
+class Engine;
+
+/**wraps the parser routine for a literal
+\param expr the original expression string
+\param engine the engine that is calling this parser
+\param start the character (index of expr) at which the literal starts
+\returns the parsed literal (first=string representation, second=value represented by it
+
+The string representation of the literal must be verbatim from expr, so that the calling engine can determine where to continue parsing in expr.
+
+If the parser does not find a valid literal according to its rules it must return an empty string.
+*/
+typedef QPair<QString,QVariant> (*LiteralParser)(const QString&expr,Engine&engine,int start);
+
+/** \brief This class holds the character classes used by an Engine.
+
+\see Engine::characterClasses()
+
+There are three major classes of chracters:
+ - names
+  - names can be variables, constants, functions
+  - the names class has two sub-classes
+   - start-of-names is characters that may start a name, usually letters and underscores
+   - all-of-names is characters that may be contained anywhere in names, this class must include the complete start of names class
+ - operators
+  - any sequence of these characters is interpreted as unary or binary operator
+ - whitespace
+  - whitespace can separate other tokens, but is ignored itself
+
+None of these three classes may overlap. There are several more minor classes:
+ - literals
+  - the character class actually only contains characters that can start a literal, the continuation and end of literals is determines by a callback routine
+  - characters in this class may overlap with names, but must not overlap with operators, whitespace and start-of-names
+ - parentheses
+  - are two distinct characters that group expressions, none of them must be contained in any other class
+ - comma
+  - a single character separating arguments in functions, the comma character must not be contained in any other class
+ - assignment
+  - one or two characters that denote an assignment
+  - both are optional, both must be contained in operators
+  - one of the characters, if it exists, is the first character of an assignment
+  - the other of the characters, if it exists, is the last character of an assignment
+  - just those two characters are the simple assignment operator
+  - both surrounding another operator combine the operation with assignment (e.g. a+=b is equivalent to a=a+b)
+
+*/
 class CharacterClassSettings
 {
        DECLARE_SHARED_DPTR(d)
        public:
+               ///class of operator characters
                QString operatorClass()const;
+               ///sets the class of operator characters
                void setOperatorClass(QString);
                
+               /**returns the two name sub-classes
+               nameClass().first refers to start characters, 
+               nameClass().second refers to all characters that can turn up anywhere in names*/
                QPair<QString,QString> nameClass()const;
+               /**sets the two sub-classes of names
+               \param startchars are characters that can start a name
+               \param allchars are characters that can turn up anywhere in names, allchars must include all characters from startchars*/
                void setNameClass(QString startchars,QString allchars);
                
+               ///returns all characters that are regarded as whitespace
                QString whitespaceClass()const;
+               ///sets all characters that are regarded as whitespace
                void setWhitespaceClass(QString);
                
+               ///returns all characters that start a literal, normally digits ' and "
                QString literalStartClass()const;
+               ///sets characters that can start a literal
                void setLiteralStartClass(QString);
                
+               ///returns the opening (first) and closing (second) character of parentheses
                QPair<QChar,QChar> parenthesesChars()const;
-               void setParentheses(QChar,QChar);
+               /**sets the characters used for parentheses
+               \param open the character that opens/begins a parentheses structure, normally "("
+               \param close the character that closes/ends a parentheses structure, normally ")"
                
+               The parentheses characters must not be included in any other class.*/
+               void setParentheses(QChar open,QChar close);
+               
+               /** \brief returns the characters that designate an assignment
+               
+               - assignmentChars().first is the character that starts an assignment operator,
+               - assignmentChars().second is the character that ends it.*/
                QPair<QChar,QChar> assignmentChars()const;
-               void setAssignmentChars(QChar,QChar);
                
+               /** sets the characters used for assignment operators
+               \param start if not '\0' the character that marks the start of an assignment
+               \param end if not '\0' the character that marks the end of an assignment
+               
+               The combination of both characters along (without whitespace) is the direct assignment operator. In automatic assignment operator mode any operator that starts with the start character and ends with end character is regarded as an implicit assignment.
+               
+               In the default configuration the start character is not set ('\0') and the end character is '=', so "a=1" will assign the value "1" to the variable "a" and "a += 1" is equivalent to "a = a + 1".
+               
+               You can turn this automatism around by defining a start character only (e.g. start='=' and end='\0'). Then the assignment would still be "a = 1", but the combination of assignment and "+" would become "a =+ 1".
+               
+               If you define both characters then both must be present in assignments. For example with start=':' and end='=' then assignment becomes "a:=1" and assignment with "+" becomes "a :+= 1".
+               
+               If set, both characters must be part of the operator class.
+               
+               If both start and end are '\0' it will be impossible to make assignments*/
+               void setAssignmentChars(QChar start,QChar end);
+               
+               ///returns the character used as a comma (separator of function arguments)
                QChar commaChar()const;
+               /**sets the character used as comma
+               
+               The character must not be part of any other class.*/
                void setCommaChar(QChar);
                
+               /**true if the settings are internally consistent
+               
+               The character class settings are consistent if all constraints of class inclusion and exclusion are fullfilled.*/
                bool isConsistent()const;
                
-               Token::Type charType(QChar,Token::Type)const;
+               /**returns the type of token the character belongs to
+               \param ch the character to be checked
+               \param oldtype the type the previous character belongs to (set to Invalid if this is the first character)
+               
+               Compares the character with the known settings for character classes and returns its probable token type. The old type of the previous character is necessary to check for context sensitive rules.
+               
+               \returns the type of this character:
+                - Invalid is returned if the character does not match any class, parsing should stop here
+                - If Literal is returned the calling engine must use specialized literal checking functions to find the end of the literal, the next character checke with this routine should be the first one behind the end of the literal
+                - Assignment characters are returned as Operator class
+                - Whitespace class characters should be ignored
+                - Special character types (Par*, Comma) must be tokenized separately
+                - Any other type (Name, Operator) must be concatenated until the return type changes
+               */
+               Token::Type charType(QChar ch,Token::Type oldtype)const;
+               
+               ///returns true if the string contains any assignment operator
+               bool isAssignment(QString)const;
+               ///returns true if the string contains exactly the simple assignment operator
+               bool isSimpleAssignment(QString)const;
+               
+               ///returns true if the string can be interpreted as name
+               bool isName(QString)const;
+               
+               ///returns true if the string can be interpreted as operator
+               bool isOperator(QString)const;
 };
 
 /**The calculation engine of .
@@ -124,6 +259,70 @@ class Engine:public QObject
                ///instantiates an engine object
                Engine(QObject*parent=0);
                
+               ///true if the named variable exists in this engine
+               bool hasVariable(QString)const;
+               ///true if the named constant exists in this engine
+               bool hasConstant(QString)const;
+               ///true if a variable or constant of that name exists in this engine
+               bool hasValue(QString)const;
+               
+               ///returns the value of the named variable or constant
+               QVariant getValue(QString)const;
+               ///returns the value of the named variable (does not return constants)
+               QVariant getVariable(QString)const;
+               ///returns the value of the named constant (does not return variables)
+               QVariant getConstant(QString)const;
+               
+               /**sets a variable
+               
+               \returns true on success or false if:
+                - the name is not valid
+                - a function or constant of that name already exists
+               */
+               bool setVariable(QString,QVariant);
+               /**sets a constant
+               
+               \returns true on success or false if:
+                - the name is not valid
+                - a function of that name exists
+               
+               Constants overwrite variables - if a variable of the same name exists, it is transparently deleted before the constant is created.*/
+               bool setConstant(QString,QVariant);
+
+               ///deletes a variable (does not affect constants)
+               void removeVariable(QString);
+               ///deletes a constant (does not affect variables)
+               void removeConstant(QString);
+               ///deletes a variable or constant
+               void removeValue(QString);
+               
+               ///returns true if the named function exists
+               bool hasFunction(QString)const;
+               ///returns the pointer to the function
+               Function getFunction(QString)const;
+               /**sets the function
+               \returns true on success or false if:
+                - the name is not valid
+                - the function is null
+                - a constant or variable of the same name exists already*/
+               bool setFunction(QString,Function);
+               ///removes a function
+               void removeFunction(QString);
+
+               /**sets the parser routine for a literal value
+               \param parser pointer to the parser routine
+               \param startchars characters that the literal can start with, all of those characters must be part of the literalStart class
+               \param prio a value between 0 and 100, parsers with higher values are preferred over those with lower values if they share a start character
+               \returns true if the parser is (re-)registered successfully, or false if:
+                - the parser is null
+                - the start characters are empty
+                - the priority is outside the allowed range (0<=prio<=100)
+               
+               If a parser function is registered a second time the new registration overwrites the old registration.
+               */
+               bool setLiteralParser(LiteralParser parser,QString startchars,int prio=50);
+               ///removes a parser function
+               void removeLiteralParser(LiteralParser parser);
        public slots:
                ///simply parses an expression string into an  object
                Expression expression(QString);
@@ -137,6 +336,9 @@ class Engine:public QObject
                QVariant evaluate(Expression);
                ///gives access to the character classes settings
                CharacterClassSettings characterClasses();
+       private:
+               ///parse a literal
+               QPair<QString,QVariant>parseLiteral(QString,int);
 };
 
 //end of namespace
index 927683e..358cd81 100644 (file)
@@ -1,5 +1,7 @@
 #include "elamexpression.h"
 
+#include <QDebug>
+
 namespace ELAM {
 
 class DPTR_CLASS_NAME(Token):public DPtr
@@ -37,4 +39,21 @@ Token::Type Token::type()const{return d->type;}
 QVariant Token::literalValue()const{return d->val;}
 Position Token::position()const{return d->pos;}
 
+QDebug&operator<<(QDebug&dbg,const Token&tok)
+{
+       dbg.nospace()<<"Token(str="<<tok.content()<<",type=";
+       switch(tok.type()){
+               case Token::Invalid:dbg<<"Invalid";break;
+               case Token::Name:dbg<<"Name";break;
+               case Token::Operator:dbg<<"Operator";break;
+               case Token::ParClose:dbg<<"OpeningParenthesis";break;
+               case Token::ParOpen:dbg<<"ClosingParenthesis";break;
+               case Token::Comma:dbg<<"Comma";break;
+               case Token::Literal:dbg<<"LiteralValue"<<",value="<<tok.literalValue();break;
+               case Token::Whitespace:dbg<<"WhiteSpace";break;
+       }
+       dbg<<",pos="<<tok.position()<<")";
+       return dbg.space();
+}
+
 };
\ No newline at end of file
index ff4812f..d885ab8 100644 (file)
@@ -11,6 +11,9 @@
 
 namespace ELAM {
 
+/**Represents a single token in a parsed expression.
+
+Tokens are pretty stupid themselves - they just know their type, position, their original piece of text and an optional value (literals). They are used by the engine and expressions to transform text into executable expressions.*/
 class Token
 {
        DECLARE_DPTR(d)
@@ -50,6 +53,8 @@ class Token
                Position position()const;
 };
 
+QDebug& operator<<(QDebug&,const Token&);
+
 class Expression
 {
        public:
index cbed42e..1504a16 100644 (file)
@@ -5,12 +5,56 @@
 
 #include "elamintengine.h"
 
-ELAM::IntEngine::IntEngine()
+using namespace ELAM;
+
+IntEngine::IntEngine()
 {
        configureIntEngine(*this);
 }
 
-void ELAM::IntEngine::configureIntEngine(ELAM::Engine& eng)
+//types of int
+// decimal: [1-9][0-9]*
+// octal: 0[0-7]*
+// hex: 0x[0-9a-fA-F]+
+QPair<QString,QVariant> IntLiteralParser(const QString&expr,Engine&engine,int start)
+{
+       QString ls;
+       //shortcut: single char?
+       if(start==(expr.size()-1)){
+               ls=expr.mid(start,1);
+               return QPair<QString,QVariant>(ls,ls.toInt());
+       }
+       //check type
+       if(expr[start]>'0' && expr[start]<='9'){
+               //decimal
+               for(int i=start;i<expr.size();i++){
+                       if(expr[i]<'0' || expr[i]>'9')break;
+                       ls+=expr[i];
+               }
+       }else //oct or hex
+       if(expr[start+1]=='x'){
+               //hex
+               ls="0x";
+               for(int i=start+2;i<expr.size();i++){
+                       if((expr[i]>='0' && expr[i]<='9')&&
+                          (expr[i]>='a' && expr[i]<='f')&&
+                          (expr[i]>='A' && expr[i]<='F'))
+                               ls+=expr[i];
+                       else
+                               break;
+               }
+       }else{
+               //oct
+               for(int i=start;i<expr.size();i++){
+                       if(expr[i]<'0' || expr[i]>'7')break;
+                       ls+=expr[i];
+               }
+       }
+       return QPair<QString,QVariant>(ls,ls.toLongLong(0,0));
+}
+
+void IntEngine::configureIntEngine(ELAM::Engine& eng)
 {
        //TODO: implement
+       eng.setLiteralParser(IntLiteralParser,"0123456789",40);
 }
index e60429b..943b10d 100644 (file)
@@ -5,6 +5,8 @@
 
 #include "elamvalue.h"
 
+#include<QDebug>
+
 namespace ELAM {
        
 Exception::Exception()
@@ -20,6 +22,7 @@ Exception::Exception(ErrorType tp,QString errText, Position pos)
        merr=errText;
        mpos=pos;
 }
+
 static int Exception_metaid=qRegisterMetaType<Exception>();
 int Exception::metaTypeId()
 {
@@ -38,5 +41,13 @@ int AnyType::metaTypeId()
        return AnyType_metaid;
 }
 
+QDebug& operator<< ( QDebug& dbg, const ELAM::Position& pos)
+{
+       if(!pos.isValid())dbg.nospace()<<"Position(invalid)";
+       else dbg.nospace()<<"Position(line="<<pos.line()<<",col="<<pos.column()<<")";
+       return dbg.space();
+}
+
+
 //end of namespace
 };
\ No newline at end of file
index 384a73d..dcb791c 100644 (file)
 
 namespace ELAM {
 
+/** \brief A character position inside a text/string that is being evaluated.
+
+A position consists of line and column. The top left position in a text is assumed to be line 1 column 1. Any position with line and/or column less than zero is considered invalid.
+
+This class is completely inline and as such should be quite fast.*/
 class Position
 {
        public:
-               Position(const QPair<int,int>&p){mline=p.first;mcol=p.second;}
-               Position(const QPoint &p){mline=p.y();mcol=p.x();}
-               Position(int line,int col){mline=line;mcol=col;}
-               Position(int col){mline=1;mcol=col;}
-               Position(){mline=mcol=-1;}
+               ///converts a pair of ints to a position
+               inline Position(const QPair<int,int>&p){mline=p.first;mcol=p.second;}
+               ///converts a QPoint to a position - x is interpreted as column, y as line
+               inline Position(const QPoint &p){mline=p.y();mcol=p.x();}
+               ///instantiates a position from line and column
+               inline Position(int line,int col){mline=line;mcol=col;}
+               ///instantiates a position from column only, line is assumed to be "1"
+               inline Position(int col){mline=1;mcol=col;}
+               ///instantiates an invalid position
+               inline Position(){mline=mcol=-1;}
+               ///copy constructor
+               inline Position(const Position&p){mline=p.mline;mcol=p.mcol;}
+               
+               ///copies a position
+               inline Position& operator=(const Position&p){mline=p.mline;mcol=p.mcol;return *this;}
+               
+               ///returns the line of this position or -1 if the position is invalid
+               inline int line()const{if(mcol>=0)return mline;else return -1;}
+               ///returns the column of this position or -1 if the position is invalid
+               inline int column()const{if(mline>=0)return mcol;else return -1;}
                
-               int line()const{return mline;}
-               int column()const{return mcol;}
+               ///true if this position is valid
+               inline bool isValid()const{return mline>=0 && mcol >=0;}
                
-               operator QPair<int,int>()const{return QPair<int,int>(mline,mcol);}
-               operator QPoint()const{return QPoint(mcol,mline);}
+               ///transparently converts to a pair of ints
+               inline operator QPair<int,int>()const{return QPair<int,int>(mline,mcol);}
+               ///transparently converts to a QPoint, with x=column and y=line
+               inline operator QPoint()const{return QPoint(mcol,mline);}
        private:
                int mline,mcol;
 };
+QDebug&operator<<(QDebug&,const Position&);
 
 /**Objects of this class represent an exception in the evaluation of an ELAM expression.*/
 class Exception
diff --git a/tests/parser/parser.cpp b/tests/parser/parser.cpp
new file mode 100644 (file)
index 0000000..857f639
--- /dev/null
@@ -0,0 +1,82 @@
+#include "elam.h"
+
+#include "parser.h"
+
+#include <QtCore>
+#include <QtTest>
+#include <QDebug>
+
+using namespace ELAM;
+
+void ElamTest::charClass()
+{
+       CharacterClassSettings def;
+       //consistency of default
+       QCOMPARE(def.isConsistent(),true);
+       //name class
+       QCOMPARE(def.isName("halloWelt0_x"),true);
+       QCOMPARE(def.isName("_halloWelt0"),true);
+       QCOMPARE(def.isName("0halloWelt0_x"),false);
+       //operator class
+       QCOMPARE(def.isOperator("=*+-"),true);
+       QCOMPARE(def.isOperator("a-+"),false);
+       //right side assigment
+       QCOMPARE(def.isAssignment("="),true);
+       QCOMPARE(def.isAssignment("+="),true);
+       QCOMPARE(def.isAssignment("=+"),false);
+       QCOMPARE(def.isSimpleAssignment("="),true);
+       QCOMPARE(def.isSimpleAssignment("+="),false);
+       //left side assigment
+       def.setAssignmentChars('=',0);
+       QCOMPARE(def.isAssignment("="),true);
+       QCOMPARE(def.isAssignment("=+"),true);
+       QCOMPARE(def.isAssignment("+="),false);
+       QCOMPARE(def.isSimpleAssignment("="),true);
+       QCOMPARE(def.isSimpleAssignment("=+"),false);
+       //two sided assigment
+       def.setAssignmentChars(':','=');
+       QCOMPARE(def.isAssignment(":="),true);
+       QCOMPARE(def.isAssignment("="),false);
+       QCOMPARE(def.isAssignment(":"),false);
+       QCOMPARE(def.isAssignment(":+="),true);
+       QCOMPARE(def.isAssignment(":=+"),false);
+       QCOMPARE(def.isAssignment("+:="),false);
+       QCOMPARE(def.isSimpleAssignment(":="),true);
+       QCOMPARE(def.isSimpleAssignment(":+="),false);
+       QCOMPARE(def.isSimpleAssignment(":"),false);
+       QCOMPARE(def.isSimpleAssignment("="),false);
+       //collision between name/operator
+       def.setOperatorClass("+-abcd");
+       QCOMPARE(def.isConsistent(),false);
+       //collision between name/literal
+       def=CharacterClassSettings();
+       def.setLiteralStartClass("abcd");
+       QCOMPARE(def.isConsistent(),false);
+       //collision between literal/operator
+       def.setLiteralStartClass("+-");
+       QCOMPARE(def.isConsistent(),false);
+       //collision between parentheses and names/ops/literals/itself
+       def=CharacterClassSettings();
+       def.setParentheses('a','b');
+       QCOMPARE(def.isConsistent(),false);
+       def.setParentheses('+','-');
+       QCOMPARE(def.isConsistent(),false);
+       def.setParentheses('0','1');
+       QCOMPARE(def.isConsistent(),false);
+       def.setParentheses('(','(');
+       QCOMPARE(def.isConsistent(),false);
+}
+
+void ElamTest::tokenizer()
+{
+       IntEngine ie;
+       QString ex="a= bcd +345*efg*(65/(5))";
+       QList<Token> tl=ie.tokenize(ex);
+       /*qDebug()<<"expression:"<<ex<<"tokens:"<<tl.size();
+       for(int i=0;i<tl.size();i++)
+               qDebug()<<" token"<<i<<tl[i];*/
+       QCOMPARE(tl.size(),15);
+}
+
+
+QTEST_MAIN(ElamTest)
\ No newline at end of file
diff --git a/tests/parser/parser.h b/tests/parser/parser.h
new file mode 100644 (file)
index 0000000..3c0e398
--- /dev/null
@@ -0,0 +1,9 @@
+#include <QObject>
+
+class ElamTest:public QObject
+{
+       Q_OBJECT
+       private slots:
+               void charClass();
+               void tokenizer();
+};
diff --git a/tests/parser/parser.pro b/tests/parser/parser.pro
new file mode 100644 (file)
index 0000000..87012c4
--- /dev/null
@@ -0,0 +1,10 @@
+TEMPLATE = app
+TARGET = parsertest
+QT -= gui
+CONFIG += qtestlib debug
+INCLUDEPATH += . ../../src
+DEPENDPATH += $$INCLUDEPATH
+LIBS += -L../.. -lelam
+
+SOURCES += parser.cpp
+HEADERS += parser.h
\ No newline at end of file