From 5ee27a50bfe161e4237d1e2276961bdc6acaecb7 Mon Sep 17 00:00:00 2001 From: konrad Date: Sun, 21 Nov 2010 20:27:31 +0000 Subject: [PATCH] enable evaluation git-svn-id: https://silmor.de/svn/softmagic/elam/trunk@638 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33 --- src/elambinary.cpp | 73 +++++++++++----- src/elambinary.h | 3 + src/elamboolengine.cpp | 6 +- src/elamengine.cpp | 15 +++ src/elamengine.h | 5 + src/elamexpression.cpp | 216 +++++++++++++++++++++++++++++++++++++++++------ src/elamexpression.h | 26 +++++- src/elamunary.cpp | 13 ++- src/elamunary.h | 3 + src/elamvalue.cpp | 38 ++++++++- src/elamvalue.h | 15 +++- tests/eval/eval.cpp | 58 ++++++++++++- tests/eval/eval.h | 3 + tests/parser/parser.cpp | 2 +- 14 files changed, 413 insertions(+), 63 deletions(-) diff --git a/src/elambinary.cpp b/src/elambinary.cpp index 85a16f4..8fc47b3 100644 --- a/src/elambinary.cpp +++ b/src/elambinary.cpp @@ -42,57 +42,86 @@ BinaryOperator::~BinaryOperator() QVariant BinaryOperator::execute ( const QVariant&op1,const QVariant&op2 ) const { - //TODO: implement - return QVariant(); + //search for match + QPairk(op1.userType(),op2.userType()); + QPairkl(k.first,AnyType::metaTypeId()); + QPairkr(AnyType::metaTypeId(),k.second); + QPairka(AnyType::metaTypeId(),AnyType::metaTypeId()); + BinaryOperatorCall bc=0; + //search for perfect, then left, then right, then any match + if(d->callmap.contains(k))bc=d->callmap[k];else + if(d->callmap.contains(kl))bc=d->callmap[kl];else + if(d->callmap.contains(kr))bc=d->callmap[kr];else + if(d->callmap.contains(ka))bc=d->callmap[ka]; + //bail out if none found + if(bc==0) + return Exception(Exception::TypeMismatchError, "operator cannot work on this type"); + //execute + return bc(op1,op2); } BinaryOperatorCall BinaryOperator::getCallback ( QString type1, QString type2 ) const { - //TODO: implement - return 0; + QPairk( QVariant::nameToType(type1.toAscii().data()), QVariant::nameToType(type2.toAscii().data())); + if(d->callmap.contains(k)) + return d->callmap[k]; + else + return 0; } BinaryOperatorCall BinaryOperator::getCallback ( int type1, int type2 ) const { - //TODO: implement - return 0; + QPairk(type1,type2); + if(d->callmap.contains(k)) + return d->callmap[k]; + else + return 0; } QList< QPair< int, int > > BinaryOperator::getTypeIds() const { - //TODO: implement - return QList< QPair< int, int > > (); + return d->callmap.keys(); } QList< QPair< QString, QString > > BinaryOperator::getTypeNames() const { - //TODO: implement - return QList< QPair< QString, QString > >(); + QList > k=d->callmap.keys(); + QList >ret; + for(int i=0;i( QVariant::typeToName((QVariant::Type)k[i].first), QVariant::typeToName((QVariant::Type)k[i].second)); + return ret; } -void BinaryOperator::removeCallback ( BinaryOperatorCall ) +void BinaryOperator::removeCallback ( BinaryOperatorCall c) { - //TODO: implement - + QList > k=d->callmap.keys(); + for(int i=0;icallmap[k[i]]==c) + d->callmap.remove(k[i]); } -void BinaryOperator::removeCallback ( QString , QString ) +void BinaryOperator::removeCallback ( QString t1, QString t2) { - //TODO: implement - + removeCallback( QVariant::nameToType(t1.toAscii().data()), QVariant::nameToType(t2.toAscii().data())); } -void BinaryOperator::removeCallback ( int , int ) +void BinaryOperator::removeCallback ( int t1, int t2) { - //TODO: implement - + d->callmap.remove(QPair(t1,t2)); } void BinaryOperator::setCallback ( BinaryOperatorCall callback, QString type1, QString type2 ) { - //TODO: implement - + setCallback(callback, QVariant::nameToType(type1.toAscii().data()), QVariant::nameToType(type2.toAscii().data())); } void BinaryOperator::setCallback ( BinaryOperatorCall callback, int type1, int type2 ) { - //TODO: implement + if(callback==0) + d->callmap.remove(QPair(type1,type2)); + else + d->callmap.insert(QPair(type1,type2),callback); +} +bool BinaryOperator::isNull() const +{ + return d->callmap.isEmpty(); } + //end of namespace }; \ No newline at end of file diff --git a/src/elambinary.h b/src/elambinary.h index 055b749..ccbe70a 100644 --- a/src/elambinary.h +++ b/src/elambinary.h @@ -63,6 +63,9 @@ class BinaryOperator ///calls the callback function associated with the type of the argument, throws an exception if there is no callback QVariant execute(const QVariant&,const QVariant&)const; + + ///true if this operator has no callbacks + bool isNull()const; }; //end of namespace diff --git a/src/elamboolengine.cpp b/src/elamboolengine.cpp index f577b20..98f9ba5 100644 --- a/src/elamboolengine.cpp +++ b/src/elamboolengine.cpp @@ -109,21 +109,21 @@ static QVariant isExceptionFunc(const QList&lf) { if(lf.size()!=1) return Exception(Exception::ArgumentListError, "expecting exactly one argument"); - return lf[0].type()==Exception::metaTypeId(); + return lf[0].userType()==Exception::metaTypeId(); } static QVariant isExceptionOrNullFunc(const QList&lf) { if(lf.size()!=1) return Exception(Exception::ArgumentListError, "expecting exactly one argument"); - return lf[0].isNull() || lf[0].type()==Exception::metaTypeId(); + return lf[0].isNull() || lf[0].userType()==Exception::metaTypeId(); } static QVariant catchFunc(const QList&lf) { if(lf.size()<1||lf.size()>3) return Exception(Exception::ArgumentListError, "expecting 1-3 arguments"); - if(lf[0].type()==Exception::metaTypeId()){ + if(lf[0].userType()==Exception::metaTypeId()){ if(lf.size()>1)return lf[1]; else return true; }else{ diff --git a/src/elamengine.cpp b/src/elamengine.cpp index bba3723..dabbd6b 100644 --- a/src/elamengine.cpp +++ b/src/elamengine.cpp @@ -301,6 +301,9 @@ BinaryOperator Engine::binaryOperator ( QString name,int prio,PriorityMatch matc //check whether prio matches if(d->binary[name].prio==prio) return d->binary[name].op; + //override if the op is empty + if(d->binary[name].op.isNull()) + match=OverridePrio; //check matching mode switch(match){ case OverridePrio: @@ -348,6 +351,18 @@ bool Engine::isAssignment(QString name) const return d->cclass.isAssignment(name); } +bool Engine::hasBinaryOperator(QString name) const +{ + if(!d->binary.contains(name))return false; + return ! d->binary[name].op.isNull(); +} + +bool Engine::hasUnaryOperator(QString name) const +{ + if(!d->unary.contains(name))return false; + return ! d->unary[name].isNull(); +} + //end of namespace }; \ No newline at end of file diff --git a/src/elamengine.h b/src/elamengine.h index c828c20..d186d70 100644 --- a/src/elamengine.h +++ b/src/elamengine.h @@ -68,6 +68,11 @@ class Engine:public QObject Q_INVOKABLE bool hasFunction(QString)const; ///returns the pointer to the function Q_INVOKABLE Function getFunction(QString)const; + + ///returns true if there is an unaryOperator with this name + Q_INVOKABLE bool hasUnaryOperator(QString name)const; + ///returns true if there is an binaryOperator with this name + Q_INVOKABLE bool hasBinaryOperator(QString name)const; /** \brief returns an existing or new unary operator object \param name the name token of the operator, if the name is not a valid operator it cannot be called from this engine until character classes change*/ diff --git a/src/elamexpression.cpp b/src/elamexpression.cpp index eb5781e..d87a3f1 100644 --- a/src/elamexpression.cpp +++ b/src/elamexpression.cpp @@ -81,6 +81,7 @@ static void printtokenlist(QDebug&dbg,const QList&tok,int llevel,int tlev for(int i=0;i&tok) +QDebug&operator<<(QDebug dbg,const QList&tok) { dbg.nospace(); printtokenlist(dbg,tok,0,0); @@ -267,6 +271,7 @@ inline Exception Expression::scanForError(const QList< Token >& toks) Expression::Expression(Engine* parent, const QList< Token >& toks) { + d->parent=parent; //check for simple errors ELAM::Exception ex=scanForError(toks); if(ex.errorType()!=ELAM::Exception::NoError){ @@ -275,9 +280,8 @@ Expression::Expression(Engine* parent, const QList< Token >& toks) d->tokens=toks; return; } - d->parent=parent; d->tokens=reduceTokens(toks); - qDebug()<<"tokens:"<tokens; +// qDebug()<<"tokens:"<tokens; //check for nothing and complain if(d->tokens.size()==0){ d->type=Exception; @@ -288,12 +292,12 @@ Expression::Expression(Engine* parent, const QList< Token >& toks) if(d->tokens.size()==1){ switch(d->tokens[0].type()){ case Token::Function: + d->type=Function; functionInit(); - d->type=(Type)d->tokens[0].type(); break; case Token::Parentheses: d->subexpr<tokens[0].subTokens()); - d->type=(Type)d->tokens[0].type(); + d->type=Parentheses; break; case Token::Constant: case Token::Variable: @@ -330,6 +334,9 @@ Expression::Expression(Engine* parent, const QList< Token >& toks) return; } //ok + d->type=AssignmentOp; + d->oppos=i; + d->subexpr<tokens.mid(0,1)); d->subexpr<tokens.mid(2)); return; } @@ -359,23 +366,28 @@ Expression::Expression(Engine* parent, const QList< Token >& toks) //split operation d->oppos=cpos; if(d->tokens[cpos].type()==Token::BinaryOp){ + d->type=BinaryOp; d->subexpr<tokens.mid(0,cpos)); d->subexpr<tokens.mid(cpos+1)); }else{ + d->type=UnaryOp; d->subexpr<tokens.mid(cpos+1)); } } void Expression::functionInit() { - QListsub=d->tokens[0].subTokens(); + //create hierarchy + QListsub=reduceTokens(d->tokens[0].subTokens()); + //scan for commas QListpar; for(int i=0;isubexpr<parent,par); par.clear(); - } + }else par<0){ d->subexpr<parent,par); } @@ -384,43 +396,195 @@ void Expression::functionInit() QVariant Expression::evaluate() { - if(d->type==Exception)return d->excep; - if(d->parent.isNull())return ELAM::Exception(ELAM::Exception::OperationError,"Lost engine context, cannot evaluate."); - return QVariant(); + //basic checks + if(d->type==Exception) + return d->excep; + if(d->parent.isNull()) + return ELAM::Exception(ELAM::Exception::OperationError,"Lost engine context, cannot evaluate."); + //check type + switch(d->type){ + case Literal:return d->tokens[0].literalValue(); + case Variable: + case Constant:{ + QString n=d->tokens[0].content(); + if(d->parent->hasValue(n)) + return d->parent->getValue(n); + else + return ELAM::Exception(ELAM::Exception::UnknownValueError, "unknown variable or constant", d->tokens[0].position()); + } + case Parentheses:return d->subexpr[0].evaluate(); + case Function:return evalFunction(); + case UnaryOp:return evalUnary(); + case BinaryOp: + return evalBinary(); + case AssignmentOp: + return evalAssign(); + default: + return ELAM::Exception(ELAM::Exception::OperationError, "internal error: unknown expression type", position()); + } } -void printExpression(QDebug&dbg,const Expression&ex,int level) +QVariant Expression::evalBinary() +{ + QString un=d->tokens.value(d->oppos).content(); + if(d->type==AssignmentOp) + un=d->parent->characterClasses().toOperator(un); + if(!d->parent->hasBinaryOperator(un)) + return ELAM::Exception(ELAM::Exception::UnknownOperatorError,"unknown operator", position()); + //get sub-expression and check for exception + QVariant sub1=d->subexpr[0].evaluate(); + if(sub1.userType()==ELAM::Exception::metaTypeId()) + return sub1; + QVariant sub2=d->subexpr[1].evaluate(); + if(sub2.userType()==ELAM::Exception::metaTypeId()) + return sub2; + //get operator + BinaryOperator binop=d->parent->binaryOperator(un); + //perform operation + QVariant r=binop.execute(sub1,sub2); + if(r.userType()==Exception::metaTypeId()){ + ELAM::Exception ex=r; + if(!ex.position().isValid()) + ex.setPosition(position()); + } + return r; +} + +QVariant Expression::evalAssign() +{ + QString un=d->tokens.value(d->oppos).content(); + QVariant r; + //execute operations to gain result + if(d->parent->characterClasses().isSimpleAssignment(un)) + r=d->subexpr[1].evaluate(); + else + r=evalBinary(); + //check for exception + if(r.userType()!=ELAM::Exception::metaTypeId()) + d->parent->setVariable(d->tokens[0].content(),r); + //return result + return r; +} + + +QVariant Expression::evalFunction() { + //basic checks + QString fn=d->tokens[0].content(); + ELAM::Function func=d->parent->getFunction(fn); + if(func==0) + return ELAM::Exception(ELAM::Exception::UnknownFunctionError, "unknown function", position()); + //gather arguments + QListargs; + for(int i=0;isubexpr.size();i++) + args<subexpr[i].evaluate(); + //execute + return func(args); +} + +QVariant Expression::evalUnary() +{ + QString un=d->tokens.value(d->oppos).content(); + if(!d->parent->hasUnaryOperator(un)) + return ELAM::Exception(ELAM::Exception::UnknownOperatorError,"unknown operator", position()); + //get sub-expression + QVariant sub=d->subexpr[0].evaluate(); + //check for exception + if(sub.userType()==ELAM::Exception::metaTypeId()) + return sub; + //get operator + UnaryOperator unop=d->parent->unaryOperator(un); + //perform operation + return unop.execute(sub); +} + + +Position Expression::position() const +{ + switch(d->type){ + case Expression::Literal: + case Expression::Variable: + case Expression::Constant: + case Expression::Function: + case Expression::Parentheses: + return d->tokens.value(0).position(); + case Expression::UnaryOp: + case Expression::BinaryOp: + case Expression::AssignmentOp: + return d->tokens.value(d->oppos).position(); + case Expression::Exception: + return d->excep.errorPos(); + default: + return Position(); + } +} + + +void Expression::printExpression(QDebug&dbg,const Expression&ex,int level) +{ + dbg.nospace(); printspaces(dbg,level); dbg<<"Expression(type="; + int ptok=-1; switch(ex.d->type){ - case Expression::Literal:dbg<<"Literal";break; - case Expression::Variable:dbg<<"Variable";break; - case Expression::Constant:dbg<<"Constant";break; - case Expression::Function:dbg<<"Function";break; - case Expression::Parentheses:dbg<<"Parentheses";break; - case Expression::UnaryOp:dbg<<"UnaryOperator";break; - case Expression::BinaryOp:dbg<<"BinaryOperator";break; - case Expression::AssignmentOp:dbg<<"Assignment";break; - case Expression::Exception:dbg<<"Exception";break; - default:dbg<<"Unknown:"<<(int)ex.d->type;break; + case Expression::Literal: + dbg<<"Literal"; + ptok=0; + break; + case Expression::Variable: + dbg<<"Variable"; + ptok=0; + break; + case Expression::Constant: + dbg<<"Constant"; + ptok=0; + break; + case Expression::Function: + dbg<<"Function"; + ptok=0; + break; + case Expression::Parentheses: + dbg<<"Parentheses"; + break; + case Expression::UnaryOp: + dbg<<"UnaryOperator:" <tokens.value(ex.d->oppos).content() <<"sub1"; + break; + case Expression::BinaryOp: + dbg<<"BinaryOperator:sub1" <tokens.value(ex.d->oppos).content() <<"sub2"; + break; + case Expression::AssignmentOp: + dbg<<"Assignment:sub1" <tokens.value(ex.d->oppos).content() <<"sub2"; + break; + case Expression::Exception: + dbg<<"Exception"; + break; + default: + dbg<<"Unknown:id="<<(int)ex.d->type; + break; } + dbg<<","<tokens.size()<<" tokens,pos="<excep.errorType()!=ELAM::Exception::NoError) dbg<<",exception="<excep; + if(ptok>=0){ + dbg<<",token "<tokens.value(0),level+1); + dbg.nospace(); + } if(ex.d->subexpr.size()>0){ dbg<<",subexpressions:"; for(int i=0;isubexpr.size();i++){ dbg<<"\n"; printExpression(dbg,ex.d->subexpr[i],level+1); + dbg.nospace(); } } dbg<<")"; } -QDebug& operator<<(QDebug&dbg,const Expression&ex) +QDebug& operator<<(QDebug dbg,const Expression&ex) { dbg.nospace(); - printExpression(dbg,ex,0); + Expression::printExpression(dbg,ex,0); return dbg.space(); } diff --git a/src/elamexpression.h b/src/elamexpression.h index fb8af15..cde9d65 100644 --- a/src/elamexpression.h +++ b/src/elamexpression.h @@ -99,12 +99,18 @@ class Token inline bool isLiteral()const{return type()&LiteralMask;} protected: friend class Expression; + ///makes the tokens type more specialized void setSubType(Type); + ///adds a token to the sub-token list void addSubToken(const Token&); + ///overrides the list of sub-tokens void setSubTokens(const QList&); }; -QDebug& operator<<(QDebug&,const Token&); +///makes tokens accessable to qDebug() +QDebug& operator<<(QDebug,const Token&); +///makes tokens accessable to qDebug() +QDebug& operator<<(QDebug,const QList&); class Engine; /**Represents an expression in the context of its engine. @@ -133,8 +139,13 @@ class Expression ///evaluates the expression and returns the result of the evaluation QVariant evaluate(); + + ///returns the position of the main element of this expression + Position position()const; private: - friend void printExpression(QDebug&,const Expression&,int); + friend QDebug& operator<<(QDebug,const Expression&); + ///helper for qDebug() + static void printExpression(QDebug&,const Expression&,int); ///scan tokens and decide what specific sub-type they are QListclassifyTokens(QList toks); /**pushes parentheses and function arguments into the sub-tokens of their parents; @@ -146,9 +157,18 @@ class Expression QListsimplifyTokens(QList toks); ///parses tokens and splits them by comma void functionInit(); + ///helper for evaluate - evaluates a function + QVariant evalFunction(); + ///helper for evaluate - evaluates an unary operator + QVariant evalUnary(); + ///helper for evaluate - evaluates an binary operator + QVariant evalBinary(); + ///helper for evaluate - evaluates an assignment and an optional binary operator + QVariant evalAssign(); }; -QDebug& operator<<(QDebug&,const Expression&); +///makes expressions accessable to qDebug() +QDebug& operator<<(QDebug,const Expression&); //end of namespace }; diff --git a/src/elamunary.cpp b/src/elamunary.cpp index ff828b2..6556baf 100644 --- a/src/elamunary.cpp +++ b/src/elamunary.cpp @@ -45,15 +45,15 @@ UnaryOperator::~UnaryOperator() QVariant UnaryOperator::execute(const QVariant& op) const { if(d->callmap.size()==0) - return qVariantFromValue(Exception(Exception::UnknownOperatorError)); + return Exception(Exception::UnknownOperatorError); //search for direct match - if(d->callmap.contains(op.type())) - return d->callmap[op.type()](op); + if(d->callmap.contains(op.userType())) + return d->callmap[op.userType()](op); //search for fallback int any=AnyType::metaTypeId(); if(d->callmap.contains(any)) return d->callmap[any](op); - return qVariantFromValue(Exception(Exception::TypeMismatchError)); + return Exception(Exception::TypeMismatchError); } QList< int > UnaryOperator::getTypeIds() const @@ -117,6 +117,11 @@ void UnaryOperator::removeCallback(int t) d->callmap.remove(t); } +bool UnaryOperator::isNull() const +{ + return d->callmap.isEmpty(); +} + //end of namespace }; \ No newline at end of file diff --git a/src/elamunary.h b/src/elamunary.h index 5976319..d331dbc 100644 --- a/src/elamunary.h +++ b/src/elamunary.h @@ -67,6 +67,9 @@ class UnaryOperator ///calls the callback function associated with the type of the argument, throws an exception if there is no callback QVariant execute(const QVariant&)const; + + ///true if this operator has no callbacks + bool isNull()const; }; //end of namespace diff --git a/src/elamvalue.cpp b/src/elamvalue.cpp index 1a9b63c..2b88537 100644 --- a/src/elamvalue.cpp +++ b/src/elamvalue.cpp @@ -13,6 +13,19 @@ Exception::Exception(const Exception& e) { operator=(e); } + +Exception::Exception(const QVariant& v) +{ + operator=(v.value()); +} +Exception::Exception(Exception::ErrorType type, Position pos, QString errorText) +{ + mtype=type; + merr=errorText; + mpos=pos; +} + + Exception::Exception(ErrorType tp,QString errText, Position pos) { mtype=tp; @@ -25,6 +38,7 @@ int Exception::metaTypeId() { return Exception_metaid; } + Exception& Exception::operator=(const Exception& e) { mtype=e.mtype; @@ -39,13 +53,35 @@ int AnyType::metaTypeId() return AnyType_metaid; } -QDebug& operator<< ( QDebug& dbg, const ELAM::Position& pos) +QDebug& operator<< ( QDebug dbg, const ELAM::Position& pos) { if(!pos.isValid())dbg.nospace()<<"Position(invalid)"; else dbg.nospace()<<"Position(line="<(*this);} @@ -115,6 +124,8 @@ class Exception Position mpos; }; +QDebug&operator<<(QDebug,const Exception&); + /**this type is not actually used, its ID is used as a fallback to tell operators, functions and engines that any supported type can be used*/ class AnyType { @@ -122,10 +133,12 @@ class AnyType ///returns the meta type ID of the ANY type static int metaTypeId(); }; +QDebug&operator<<(QDebug,const AnyType&); //end of namespace }; + Q_DECLARE_METATYPE(ELAM::Exception); Q_DECLARE_METATYPE(ELAM::AnyType); diff --git a/tests/eval/eval.cpp b/tests/eval/eval.cpp index d0d000a..abf866c 100644 --- a/tests/eval/eval.cpp +++ b/tests/eval/eval.cpp @@ -13,10 +13,64 @@ void ElamTest::evaltest() { IntEngine ie; FloatEngine::configureFloatEngine(ie); - QString exs="a=b+=345*int(3.5)+ - -(+65/(5))"; + ie.setVariable("b",(qlonglong)0); + QString exs="a=b+=345*int(3.15)+ - -(+65/(5))"; Expression ex=ie.expression(exs); - qDebug()<<"expression:"<&args) +{ + return (qlonglong)args.size(); +} +static QVariant mysum(const QList&args) +{ + qlonglong r=0; + for(int i=0;i #include -#include +// #include using namespace ELAM; -- 1.7.2.5