From 85e3fda6352b97774075f95fb7f1cd2d531da9b5 Mon Sep 17 00:00:00 2001 From: konrad Date: Wed, 17 Nov 2010 20:57:43 +0000 Subject: [PATCH] hierarchical tokens some docu for default lib add TODO add eval test draft git-svn-id: https://silmor.de/svn/softmagic/elam/trunk@635 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33 --- TODO | 20 ++++ doc/library.html | 55 ++++++++++- doc/syntax.html | 8 +- src/elam.h | 1 + src/elam.pro | 6 +- src/elamboolengine.cpp | 143 +++++++++++++++++++++++++++ src/elamboolengine.h | 32 ++++++ src/elamexpression.cpp | 256 ++++++++++++++++++++++++++++++++++++++++++++++-- src/elamexpression.h | 49 ++++++--- tests/eval/eval.cpp | 21 ++++ tests/eval/eval.h | 8 ++ tests/eval/eval.pro | 10 ++ 12 files changed, 579 insertions(+), 30 deletions(-) create mode 100644 TODO create mode 100644 src/elamboolengine.cpp create mode 100644 src/elamboolengine.h create mode 100644 tests/eval/eval.cpp create mode 100644 tests/eval/eval.h create mode 100644 tests/eval/eval.pro diff --git a/TODO b/TODO new file mode 100644 index 0000000..44345e4 --- /dev/null +++ b/TODO @@ -0,0 +1,20 @@ +TODO for ELAM +================ + + +Engine +--------- + +* Expression ordering and execution +* function overloading + +Default Library +------------------ + +int: abs(...) + +float: abs, round, comparison operators + +bool + +string diff --git a/doc/library.html b/doc/library.html index 7a713ed..c9bb4cc 100644 --- a/doc/library.html +++ b/doc/library.html @@ -7,9 +7,62 @@

Calling It

+

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 type 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
+ +

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
+

Basic Types, Operators, Functions and Variables

-

Extending The ElAM Libray

+

Extending The ELAM Libray

Redirecting Variables and Constants

diff --git a/doc/syntax.html b/doc/syntax.html index 73e410f..2411963 100644 --- a/doc/syntax.html +++ b/doc/syntax.html @@ -1,5 +1,5 @@ -ELAM +ELAM Syntax

The ELAM Syntax

@@ -74,6 +74,7 @@ Parentheses group operations and may alter the order in which they are executed. 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.

@@ -83,7 +84,7 @@ The default library has the following precedence order: - + @@ -93,7 +94,8 @@ The default library has the following precedence order: - + +
Operator ClassOperatorsPrecedence Value
Parentheses(parentheses), functions(...)1000
All Unary Operators ++, --, ~, !, -, +100
All Unary Operators ~, !, -, +100
Maximum usable Precedence 99
Multiplicative *, /, %90
Additive +, -80
bitwise XOR ^45
bitwise OR |40
logical AND &&30
logical OR||25
logical XOR^^25
logical OR||20
Lowest usable Precedence 1
assignments=, +=, -=, ...0

diff --git a/src/elam.h b/src/elam.h index 2c86388..165abc9 100644 --- a/src/elam.h +++ b/src/elam.h @@ -9,6 +9,7 @@ #include "elamengine.h" #include "elamintengine.h" #include "elamfloatengine.h" +#include "elamboolengine.h" /** \mainpage ELAM - Elementary Logic and Arithmetic Machine diff --git a/src/elam.pro b/src/elam.pro index 4a2709d..690e18f 100644 --- a/src/elam.pro +++ b/src/elam.pro @@ -18,7 +18,8 @@ HEADERS += \ elamexpression.h \ elamvalue.h \ elamintengine.h \ - elamfloatengine.h + elamfloatengine.h \ + elamboolengine.h SOURCES += \ elamvalue.cpp \ @@ -28,7 +29,8 @@ SOURCES += \ elamengine.cpp \ elamintengine.cpp \ elamexpression.cpp \ - elamfloatengine.cpp + elamfloatengine.cpp \ + elamboolengine.cpp INCLUDEPATH += . DEPENDPATH += . diff --git a/src/elamboolengine.cpp b/src/elamboolengine.cpp new file mode 100644 index 0000000..f577b20 --- /dev/null +++ b/src/elamboolengine.cpp @@ -0,0 +1,143 @@ +// ELAM int engine definition implementation +// +// (c) Konrad Rosenbaum, 2010 +// protected under the GNU LGPL v3 or at your option any newer + +#include "elamboolengine.h" + +#include + +using namespace ELAM; + +BoolEngine::BoolEngine() +{ + configureBoolEngine(*this); + configureLogicEngine(*this); +} + + +static QVariant boolFunc(const QList&lf) +{ + if(lf.size()!=1) + return Exception(Exception::ArgumentListError, "expecting exactly one argument"); + if(!lf[0].canConvert()) + return Exception(Exception::TypeMismatchError,"cannot convert to bool"); + return lf[0].toBool(); +} + +//unary +static QVariant boolNot(const QVariant&o) +{ + return !o.toBool(); +} + +//bitwise +static QVariant boolAnd(const QVariant&o1,const QVariant&o2) +{ + return o1.toBool()&&o2.toBool(); +} +static QVariant boolOr(const QVariant&o1,const QVariant&o2) +{ + return o1.toBool()||o2.toBool(); +} +static QVariant boolXor(const QVariant&o1,const QVariant&o2) +{ + return o1.toBool()^o2.toBool(); +} + + +void BoolEngine::configureBoolEngine(Engine&eng) +{ + int iid=QVariant::LongLong; + int bid=QVariant::Bool; + //cast + eng.setFunction("bool",boolFunc); + //constants + eng.setConstant("null",QVariant()); + eng.setConstant("true",true); + eng.setConstant("false",false); + //unaries + eng.unaryOperator("!").setCallback(boolNot,iid); + eng.unaryOperator("!").setCallback(boolNot,bid); + eng.unaryOperator("~").setCallback(boolNot,bid); + //binaries adapted + eng.binaryOperator("&",50).setCallback(boolAnd,iid,bid); + eng.binaryOperator("&",50).setCallback(boolAnd,bid,iid); + eng.binaryOperator("&",50).setCallback(boolAnd,bid,bid); + eng.binaryOperator("|",40).setCallback(boolOr,bid,iid); + eng.binaryOperator("|",40).setCallback(boolOr,iid,bid); + eng.binaryOperator("|",40).setCallback(boolOr,bid,bid); + eng.binaryOperator("^",45).setCallback(boolXor,bid,iid); + eng.binaryOperator("^",45).setCallback(boolXor,iid,bid); + eng.binaryOperator("^",45).setCallback(boolXor,bid,bid); + //booleans + eng.binaryOperator("&&",30).setCallback(boolAnd,iid,iid); + eng.binaryOperator("&&",30).setCallback(boolAnd,iid,bid); + eng.binaryOperator("&&",30).setCallback(boolAnd,bid,iid); + eng.binaryOperator("&&",30).setCallback(boolAnd,bid,bid); + eng.binaryOperator("||",20).setCallback(boolOr,iid,iid); + eng.binaryOperator("||",20).setCallback(boolOr,bid,iid); + eng.binaryOperator("||",20).setCallback(boolOr,iid,bid); + eng.binaryOperator("||",20).setCallback(boolOr,bid,bid); + eng.binaryOperator("^^",25).setCallback(boolXor,iid,iid); + eng.binaryOperator("^^",25).setCallback(boolXor,bid,iid); + eng.binaryOperator("^^",25).setCallback(boolXor,iid,bid); + eng.binaryOperator("^^",25).setCallback(boolXor,bid,bid); +} + +static QVariant ifFunc(const QList&lf) +{ + if(lf.size()<2 || lf.size()>3) + return Exception(Exception::ArgumentListError, "expecting 2 or 3 arguments"); + if(!lf[0].canConvert()) + return Exception(Exception::TypeMismatchError,"cannot convert argument 1 to bool"); + if(lf[0].toBool()) + return lf[1]; + else + if(lf.size()>2)return lf[2]; + return QVariant(); +} + +static QVariant isNullFunc(const QList&lf) +{ + if(lf.size()!=1) + return Exception(Exception::ArgumentListError, "expecting exactly one argument"); + return lf[0].isNull(); +} + +static QVariant isExceptionFunc(const QList&lf) +{ + if(lf.size()!=1) + return Exception(Exception::ArgumentListError, "expecting exactly one argument"); + return lf[0].type()==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(); +} + +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.size()>1)return lf[1]; + else return true; + }else{ + if(lf.size()>2)return lf[2]; + else return false; + } +} + + +void BoolEngine::configureLogicEngine(Engine& eng) +{ + eng.setFunction("if",ifFunc); + eng.setFunction("isNull",isNullFunc); + eng.setFunction("isException",isExceptionFunc); + eng.setFunction("isExceptionOrNull",isExceptionOrNullFunc); + eng.setFunction("catch",catchFunc); +} diff --git a/src/elamboolengine.h b/src/elamboolengine.h new file mode 100644 index 0000000..379bb32 --- /dev/null +++ b/src/elamboolengine.h @@ -0,0 +1,32 @@ +//ELAM integer engine header +// +// (c) Konrad Rosenbaum, 2010 +// protected under the GNU LGPL v3 or at your option any newer + +#ifndef ELAM_BOOLENGINE_H +#define ELAM_BOOLENGINE_H + +#include "elamengine.h" + +namespace ELAM { + +/**A boolean and logic math enabled engine. + +This engine type plays nicely with ELAM::IntEngine +*/ +class BoolEngine:public Engine +{ + public: + ///instantiates a pre-configured engine + BoolEngine(); + + ///configures any engine to support basic boolean math + static void configureBoolEngine(Engine&); + + ///configures any engine to support logic functions + static void configureLogicEngine(Engine&); +}; + +}; + +#endif diff --git a/src/elamexpression.cpp b/src/elamexpression.cpp index 60e4ca5..65d0a7b 100644 --- a/src/elamexpression.cpp +++ b/src/elamexpression.cpp @@ -1,21 +1,26 @@ #include "elamexpression.h" #include +#include +#include "elamengine.h" namespace ELAM { /////////////////////////////////////////////////////////////////////////////// // Token -class DPTR_CLASS_NAME(Token):public DPtr +class DPTR_CLASS_NAME(Token):public SharedDPtr { public: + DPTR_NAME(){type=Invalid;subtype=None;} QString cont; Type type; + SubType subtype; QVariant val; Position pos; + QListsubtok; }; -DEFINE_DPTR(Token) +DEFINE_SHARED_DPTR(Token) Token::Token(Position pos) @@ -44,9 +49,38 @@ 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) +QList< Token > Token::subTokens() const{return d->subtok;} +Token::SubType Token::subType() const{return d->subtype;} +void Token::setSubType(Token::SubType s){d->subtype=s;} +void Token::addSubToken(const ELAM::Token& t) +{ + d->subtok<& t) +{ + d->subtok=t; +} + +static void printspaces(QDebug&dbg,int level) +{ + for(int i=0;i&tok,int llevel,int tlevel) +{ + printspaces(dbg,llevel); + dbg<<"TokenList("; + for(int i=0;i&tok) +{ + dbg.nospace(); + printtokenlist(dbg,tok,0,0); + return dbg.space(); +} +/////////////////////////////////////////////////////////////////////////////// +// TokenBundle + +class TokenBundle +{ + public: + TokenBundle(const Token&); + TokenBundle(const TokenBundle&); + TokenBundle(const QList&); + private: + QList&mtoks; +}; + /////////////////////////////////////////////////////////////////////////////// // Expression class DPTR_CLASS_NAME(Expression):public SharedDPtr { + public: + DPTR_NAME(){type=Exception;} + QPointerparent; + QListtokens; + Type type; + QVariant value; }; DEFINE_SHARED_DPTR(Expression); @@ -73,9 +155,169 @@ Expression::Expression() { } -Expression::Expression(Engine* parent, const QList< Token >& tokens) + +//reduce surrounding parentheses and whitespace +static inline QListsimplifyTokens(Engine*eng,QList toks) { + Q_UNUSED(eng); + QListret; + int min=0,max=toks.size()-1; + //eliminate redundant parentheses + while(toks[min].type()==Token::ParOpen && toks[max].type()==Token::ParClose){ + min++;max--; + } + //reduce whitespace + for(int i=min;i<=max;i++){ + //eliminate whitespace + if(toks[i].type()==Token::Whitespace) + continue; + ret<classifyTokens(Engine*eng,QList toks) +{ + QListret; + if(toks.size()<1)return toks; + //check token 0 + Token t=toks[0]; + if(t.type()==Token::Name){ + if(eng->hasFunction(t.content()))t.setSubType(Token::Function);else + if(eng->hasConstant(t.content()))t.setSubType(Token::Constant); + else t.setSubType(Token::Variable); + }else if(t.type()==Token::Operator)t.setSubType(Token::UnaryOp); + ret<hasFunction(t.content()))t.setSubType(Token::Function);else + if(eng->hasConstant(t.content()))t.setSubType(Token::Constant); + else t.setSubType(Token::Variable); + }else + //define operators + if(t.type()==Token::Operator){ + switch(toks[i-1].type()){ + case Token::ParOpen: + case Token::Operator: + t.setSubType(Token::UnaryOp); + break; + default: + t.setSubType(Token::BinaryOp); + break; + } + } + //add + ret<reduceTokens(Engine*eng,QList toks) +{ + toks=classifyTokens(eng,simplifyTokens(eng,toks)); + QListret,sub; + //copy and create hierarchy + int pcnt=0; + for(int i=0;i& toks) +{ + //check for invalid tokens + for(int i=0;i& toks) +{ + //check for simple errors + ELAM::Exception ex=scanForError(toks); + if(ex.errorType()!=ELAM::Exception::NoError){ + d->type=Exception; + d->value=ex; + d->tokens=toks; + return; + } + d->parent=parent; + d->tokens=reduceTokens(parent,toks); + qDebug()<<"expression:"<tokens; + //check for nothing and complain + if(d->tokens.size()==0){ + d->type=Exception; + d->value=ELAM::Exception(ELAM::Exception::ParserError,"no tokens", (toks.size()>0?toks[0].position():Position())); + return; + } + //check for simplicity (literals, vars, consts) + if(d->tokens.size()==1){ + switch(d->tokens[0].type()){ + case Token::Name: + if(parent->hasFunction(d->tokens[0].content())){ + d->type=Exception; + d->value=ELAM::Exception(ELAM::Exception::ParserError, "function call incomplete", d->tokens[0].position()); + }else if(parent->hasConstant(d->tokens[0].content())){ + d->type=Constant; + d->value=parent->getConstant(d->tokens[0].content()); + }else{ + d->type=Variable; + } + break; + case Token::Literal: + d->type=Literal; + d->value=d->tokens[0].literalValue(); + break; + default: + d->type=Exception; + d->value=ELAM::Exception(ELAM::Exception::ParserError, "unexpected token", d->tokens[0].position()); + break; + } + return; + } } QVariant Expression::evaluate() diff --git a/src/elamexpression.h b/src/elamexpression.h index 75650e7..dff4853 100644 --- a/src/elamexpression.h +++ b/src/elamexpression.h @@ -16,26 +16,36 @@ namespace ELAM { 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) + DECLARE_SHARED_DPTR(d) public: ///The type of token enum Type { ///invalid token - Invalid, + Invalid=0, ///a name: function, variable, or constant - Name, + Name=7, ///an operator (unary or binary) - Operator, + Operator=24, + ///meta-type used for parsed sub-tokens + Parentheses=96, ///opening parenthese - ParOpen, + ParOpen=32, ///closing parenthese - ParClose, + ParClose=64, ///a comma - separating expressions in function calls - Comma, + Comma=128, ///a literal value - Literal, + Literal=256, ///white space chars, this is actually not used for tokens, but for parsing - Whitespace, + Whitespace=512, + }; + enum SubType{ + None = 0, + Function = 1, + Constant = 2, + Variable = 4, + UnaryOp = 8, + BinaryOp = 16, }; ///creates an empty/invalid token Token(Position pos=Position(-1,-1)); @@ -51,6 +61,11 @@ class Token QVariant literalValue()const; ///returns the original position of the token Position position()const; + SubType subType()const; + void setSubType(SubType); + QListsubTokens()const; + void addSubToken(const Token&); + void setSubTokens(const QList&); }; QDebug& operator<<(QDebug&,const Token&); @@ -61,14 +76,14 @@ class Expression DECLARE_SHARED_DPTR(d) public: enum Type { - Literal, - Variable, - Constant, - Function, - Parentheses, - UnaryOp, - BinaryOp, - Exception, + Literal=Token::Literal, + Variable=Token::Variable, + Constant=Token::Constant, + Function=Token::Function, + Parentheses=96, + UnaryOp=Token::UnaryOp, + BinaryOp=Token::BinaryOp, + Exception=0x8000, }; Expression(); Expression(Engine*parent,const QList&tokens); diff --git a/tests/eval/eval.cpp b/tests/eval/eval.cpp new file mode 100644 index 0000000..bc5e706 --- /dev/null +++ b/tests/eval/eval.cpp @@ -0,0 +1,21 @@ +#include "elam.h" + +#include "eval.h" + +#include +#include +#include + +using namespace ELAM; + + +void ElamTest::evaltest() +{ + IntEngine ie; + FloatEngine::configureFloatEngine(ie); + QString ex="a= 345*(65.3/(5))"; + QVariant v=ie.evaluate(ex); +} + + +QTEST_MAIN(ElamTest) \ No newline at end of file diff --git a/tests/eval/eval.h b/tests/eval/eval.h new file mode 100644 index 0000000..0ab9f81 --- /dev/null +++ b/tests/eval/eval.h @@ -0,0 +1,8 @@ +#include + +class ElamTest:public QObject +{ + Q_OBJECT + private slots: + void evaltest(); +}; diff --git a/tests/eval/eval.pro b/tests/eval/eval.pro new file mode 100644 index 0000000..9900a09 --- /dev/null +++ b/tests/eval/eval.pro @@ -0,0 +1,10 @@ +TEMPLATE = app +TARGET = evaltest +QT -= gui +CONFIG += qtestlib debug link_prl +INCLUDEPATH += . ../../src +DEPENDPATH += $$INCLUDEPATH ../.. +LIBS += -L../.. -lelam + +SOURCES += eval.cpp +HEADERS += eval.h \ No newline at end of file -- 1.7.2.5