diff --git a/src/DataFilter.cpp b/src/DataFilter.cpp index 82df1bcdc..b35a3e724 100644 --- a/src/DataFilter.cpp +++ b/src/DataFilter.cpp @@ -31,6 +31,65 @@ #include "DataFilter_yacc.h" +// v4 functions +static struct { + + QString name; + int parameters; + +} DataFilterFunctions[] = { + + // ALWAYS ADD TO BOTTOM OF LIST AS ARRAY OFFSET + // USED IN SWITCH STATEMENT AT RUNTIME DO NOT BE + // TEMPTED TO ADD IN THE MIDDLE !!!! + + // math.h + { "cos", 1 }, + { "tan", 1 }, + { "sin", 1 }, + { "acos", 1 }, + { "atan", 1 }, + { "asin", 1 }, + { "cosh", 1 }, + { "tanh", 1 }, + { "sinh", 1 }, + { "acosh", 1 }, + { "atanh", 1 }, + { "asinh", 1 }, + + { "exp", 1 }, + { "log", 1 }, + { "log10", 1 }, + + { "ceil", 1 }, + { "floor", 1 }, + { "round", 1 }, + + { "fabs", 1 }, + { "isinf", 1 }, + { "isnan", 1 }, + + // add new ones above this line + { "", -1 } +}; + +QStringList +DataFilter::functions() +{ + QStringList returning; + + for(int i=0; DataFilterFunctions[i].parameters != -1; i++) { + QString function = DataFilterFunctions[i].name + "("; + for(int j=0; jtype) { case Leaf::Float : qDebug()<<"float"<lvalue.f<dynamic; break; case Leaf::Integer : qDebug()<<"integer"<lvalue.i<dynamic; break; @@ -117,8 +180,14 @@ void Leaf::print(Leaf *leaf, int level) leaf->print(leaf->lvalue.l, level+1); leaf->print(leaf->rvalue.l, level+1); break; - case Leaf::Function : qDebug()<<"function"<function<<"parm="<<*(leaf->series->lvalue.n); - if (leaf->lvalue.l) leaf->print(leaf->lvalue.l, level+1); + case Leaf::Function : + if (leaf->series) { + qDebug()<<"function"<function<<"parm="<<*(leaf->series->lvalue.n); + if (leaf->lvalue.l) leaf->print(leaf->lvalue.l, level+1); + } else { + qDebug()<<"function"<function<<"parms:"<fparms.count(); + foreach(Leaf*l, leaf->fparms) leaf->print(l, level+1); + } break; case Leaf::Conditional : qDebug()<<"cond"; { @@ -127,6 +196,12 @@ void Leaf::print(Leaf *leaf, int level) leaf->print(leaf->rvalue.l, level+1); } break; + case Leaf::Parameters : + { + qDebug()<<"parameters"<fparms.count(); + foreach(Leaf*l, fparms) leaf->print(l, level+1); + } + break; default: break; @@ -180,6 +255,7 @@ bool Leaf::isNumber(DataFilter *df, Leaf *leaf) return true; } break; + case Leaf::Parameters : return false; break; default: return false; @@ -244,42 +320,69 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf) QRegExp tizValidSymbols("^(power|hr)$", Qt::CaseInsensitive); QRegExp configValidSymbols("^(cp|w\\'|pmax|cv|d\\'|scv|sd\\'|height|weight|lthr|maxhr|rhr|units)$", Qt::CaseInsensitive); QRegExp constValidSymbols("^(e|pi)$", Qt::CaseInsensitive); // just do basics for now - QString symbol = leaf->series->lvalue.n->toLower(); - if (leaf->function == "sts" || leaf->function == "lts" || leaf->function == "sb" || leaf->function == "rr") { + if (leaf->series) { // old way of hand crafting each function in the lexer including support for literal parameter e.g. (power, 1) - // does the symbol exist though ? - QString lookup = df->lookupMap.value(symbol, ""); - if (lookup == "") DataFiltererrors << QString(QObject::tr("%1 is unknown")).arg(symbol); + QString symbol = leaf->series->lvalue.n->toLower(); - } else { + if (leaf->function == "sts" || leaf->function == "lts" || leaf->function == "sb" || leaf->function == "rr") { - if (leaf->function == "best" && !bestValidSymbols.exactMatch(symbol)) - DataFiltererrors << QString(QObject::tr("invalid data series for best(): %1")).arg(symbol); + // does the symbol exist though ? + QString lookup = df->lookupMap.value(symbol, ""); + if (lookup == "") DataFiltererrors << QString(QObject::tr("%1 is unknown")).arg(symbol); - if (leaf->function == "tiz" && !tizValidSymbols.exactMatch(symbol)) - DataFiltererrors << QString(QObject::tr("invalid data series for tiz(): %1")).arg(symbol); + } else { - if (leaf->function == "config" && !configValidSymbols.exactMatch(symbol)) - DataFiltererrors << QString(QObject::tr("invalid data series for config(): %1")).arg(symbol); + if (leaf->function == "best" && !bestValidSymbols.exactMatch(symbol)) + DataFiltererrors << QString(QObject::tr("invalid data series for best(): %1")).arg(symbol); - if (leaf->function == "const") { - if (!constValidSymbols.exactMatch(symbol)) - DataFiltererrors << QString(QObject::tr("invalid literal for const(): %1")).arg(symbol); - else { + if (leaf->function == "tiz" && !tizValidSymbols.exactMatch(symbol)) + DataFiltererrors << QString(QObject::tr("invalid data series for tiz(): %1")).arg(symbol); - // convert to a float - leaf->type = Leaf::Float; - leaf->lvalue.f = 0.0L; - if (symbol == "e") leaf->lvalue.f = MATHCONST_E; - if (symbol == "pi") leaf->lvalue.f = MATHCONST_PI; + if (leaf->function == "config" && !configValidSymbols.exactMatch(symbol)) + DataFiltererrors << QString(QObject::tr("invalid data series for config(): %1")).arg(symbol); + + if (leaf->function == "const") { + if (!constValidSymbols.exactMatch(symbol)) + DataFiltererrors << QString(QObject::tr("invalid literal for const(): %1")).arg(symbol); + else { + + // convert to a float + leaf->type = Leaf::Float; + leaf->lvalue.f = 0.0L; + if (symbol == "e") leaf->lvalue.f = MATHCONST_E; + if (symbol == "pi") leaf->lvalue.f = MATHCONST_PI; + } + } + + if (leaf->function == "best" || leaf->function == "tiz") { + // now set the series type used as parameter 1 to best/tiz + leaf->seriesType = nameToSeries(symbol); + } + } + } else { // generic functions, math etc + + bool found=false; + + // are the parameters well formed ? + foreach(Leaf *p, leaf->fparms) validateFilter(df, p); + + // does it exist? + for(int i=0; DataFilterFunctions[i].parameters != -1; i++) { + if (DataFilterFunctions[i].name == leaf->function) { + + // with the right number of parameters? + if (leaf->fparms.count() != DataFilterFunctions[i].parameters) { + DataFiltererrors << QString(QObject::tr("function '%1' expects %2 parameter(s) not %3")).arg(leaf->function) + .arg(DataFilterFunctions[i].parameters).arg(fparms.count()); + } + found = true; + break; } } - if (leaf->function == "best" || leaf->function == "tiz") { - // now set the series type used as parameter 1 to best/tiz - leaf->seriesType = nameToSeries(symbol); - } + // not known! + if (!found) DataFiltererrors << QString(QObject::tr("unknown function %1")).arg(leaf->function); } } @@ -322,6 +425,13 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf) } break; + case Leaf::Parameters : + { + // should never get here ! + DataFiltererrors << QObject::tr("internal parser error: parms"); + } + break; + default: break; } @@ -575,8 +685,8 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m) // // LTHR, MaxHR, RHR // - int hrZoneRange = context->athlete->hrZones() ? - context->athlete->hrZones()->whichRange(m->dateTime.date()) + int hrZoneRange = context->athlete->hrZones() ? + context->athlete->hrZones()->whichRange(m->dateTime.date()) : -1; int LTHR = hrZoneRange != -1 ? context->athlete->hrZones()->getLT(hrZoneRange) : 0; @@ -586,15 +696,15 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m) // // CV' D' // - int paceZoneRange = context->athlete->paceZones(false) ? - context->athlete->paceZones(false)->whichRange(m->dateTime.date()) : + int paceZoneRange = context->athlete->paceZones(false) ? + context->athlete->paceZones(false)->whichRange(m->dateTime.date()) : -1; double CV = (paceZoneRange != -1) ? context->athlete->paceZones(false)->getCV(paceZoneRange) : 0.0; double DPRIME = 0; //XXX(paceZoneRange != -1) ? context->athlete->paceZones(false)->getDPrime(paceZoneRange) : 0.0; - int spaceZoneRange = context->athlete->paceZones(true) ? - context->athlete->paceZones(true)->whichRange(m->dateTime.date()) : + int spaceZoneRange = context->athlete->paceZones(true) ? + context->athlete->paceZones(true)->whichRange(m->dateTime.date()) : -1; double SCV = (spaceZoneRange != -1) ? context->athlete->paceZones(true)->getCV(spaceZoneRange) : 0.0; double SDPRIME = 0; //XXX (spaceZoneRange != -1) ? context->athlete->paceZones(true)->getDPrime(spaceZoneRange) : 0.0; @@ -650,51 +760,99 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m) } // get here for tiz and best - switch (leaf->lvalue.l->type) { + if (leaf->function == "best" || leaf->function == "tiz") { - default: - case Leaf::Function : - { - duration = eval(context, df, leaf->lvalue.l, m).number; // duration will be zero if string - } - break; + switch (leaf->lvalue.l->type) { - case Leaf::Symbol : - { - QString rename; - // get symbol value - if (df->lookupType.value(*(leaf->lvalue.l->lvalue.n)) == true) { - // numeric - duration = m->getForSymbol(rename=df->lookupMap.value(*(leaf->lvalue.l->lvalue.n),"")); - } else { - duration = 0; + default: + case Leaf::Function : + { + duration = eval(context, df, leaf->lvalue.l, m).number; // duration will be zero if string } + break; + + case Leaf::Symbol : + { + QString rename; + // get symbol value + if (df->lookupType.value(*(leaf->lvalue.l->lvalue.n)) == true) { + // numeric + duration = m->getForSymbol(rename=df->lookupMap.value(*(leaf->lvalue.l->lvalue.n),"")); + } else { + duration = 0; + } + } + break; + + case Leaf::Float : + duration = leaf->lvalue.l->lvalue.f; + break; + + case Leaf::Integer : + duration = leaf->lvalue.l->lvalue.i; + break; + + case Leaf::String : + duration = (leaf->lvalue.l->lvalue.s)->toDouble(); + break; + + break; } - break; - case Leaf::Float : - duration = leaf->lvalue.l->lvalue.f; - break; + if (leaf->function == "best") + return Result(RideFileCache::best(df->context, m->fileName, leaf->seriesType, duration)); - case Leaf::Integer : - duration = leaf->lvalue.l->lvalue.i; - break; - - case Leaf::String : - duration = (leaf->lvalue.l->lvalue.s)->toDouble(); - break; - - break; + if (leaf->function == "tiz") // duration is really zone number + return Result(RideFileCache::tiz(df->context, m->fileName, leaf->seriesType, duration)); } - if (leaf->function == "best") - return Result(RideFileCache::best(df->context, m->fileName, leaf->seriesType, duration)); + // if we get here its general function handling + // what function is being called? + int fnum=-1; + for (int i=0; DataFilterFunctions[i].parameters != -1; i++) { + if (DataFilterFunctions[i].name == leaf->function) { - if (leaf->function == "tiz") // duration is really zone number - return Result(RideFileCache::tiz(df->context, m->fileName, leaf->seriesType, duration)); + // parameter mismatch not allowed; function signature mismatch + // should be impossible... + if (DataFilterFunctions[i].parameters != leaf->fparms.count()) + fnum=-1; + else + fnum = i; + break; + } + } - // unknown function!? - return Result(0) ; + // not found... + if (fnum < 0) return Result(0); + + switch (fnum) { + case 0 : { return Result(cos(eval(context, df, leaf->fparms[0], m).number)); } // COS(x) + case 1 : { return Result(tan(eval(context, df, leaf->fparms[0], m).number)); } // TAN(x) + case 2 : { return Result(sin(eval(context, df, leaf->fparms[0], m).number)); } // SIN(x) + case 3 : { return Result(acos(eval(context, df, leaf->fparms[0], m).number)); } // ACOS(x) + case 4 : { return Result(atan(eval(context, df, leaf->fparms[0], m).number)); } // ATAN(x) + case 5 : { return Result(asin(eval(context, df, leaf->fparms[0], m).number)); } // ASIN(x) + case 6 : { return Result(cosh(eval(context, df, leaf->fparms[0], m).number)); } // COSH(x) + case 7 : { return Result(tanh(eval(context, df, leaf->fparms[0], m).number)); } // TANH(x) + case 8 : { return Result(sinh(eval(context, df, leaf->fparms[0], m).number)); } // SINH(x) + case 9 : { return Result(acosh(eval(context, df, leaf->fparms[0], m).number)); } // ACOSH(x) + case 10 : { return Result(atanh(eval(context, df, leaf->fparms[0], m).number)); } // ATANH(x) + case 11 : { return Result(asinh(eval(context, df, leaf->fparms[0], m).number)); } // ASINH(x) + + case 12 : { return Result(exp(eval(context, df, leaf->fparms[0], m).number)); } // EXP(x) + case 13 : { return Result(log(eval(context, df, leaf->fparms[0], m).number)); } // LOG(x) + case 14 : { return Result(log10(eval(context, df, leaf->fparms[0], m).number)); } // LOG10(x) + + case 15 : { return Result(ceil(eval(context, df, leaf->fparms[0], m).number)); } // CEIL(x) + case 16 : { return Result(floor(eval(context, df, leaf->fparms[0], m).number)); } // FLOOR(x) + case 17 : { return Result(round(eval(context, df, leaf->fparms[0], m).number)); } // ROUND(x) + + case 18 : { return Result(fabs(eval(context, df, leaf->fparms[0], m).number)); } // FABS(x) + case 19 : { return Result(isinf(eval(context, df, leaf->fparms[0], m).number)); } // ISINF(x) + case 20 : { return Result(isnan(eval(context, df, leaf->fparms[0], m).number)); } // ISNAN(x) + default: + return Result(0); + } } break; diff --git a/src/DataFilter.h b/src/DataFilter.h index 51398631f..995f37ac8 100644 --- a/src/DataFilter.h +++ b/src/DataFilter.h @@ -62,7 +62,7 @@ class Leaf { bool isNumber(DataFilter *df, Leaf *leaf); void clear(Leaf*); - enum { none, Float, Integer, String, Symbol, Logical, Operation, BinaryOperation, Function, Conditional } type; + enum { none, Float, Integer, String, Symbol, Logical, Operation, BinaryOperation, Function, Conditional, Parameters } type; union value { float f; int i; @@ -72,7 +72,8 @@ class Leaf { } lvalue, rvalue, cond; int op; - QString function; + QString function; // function + QList fparms; // passed parameters Leaf *series; // is a symbol bool dynamic; @@ -100,6 +101,8 @@ class DataFilter : public QObject // when used for formulas Result evaluate(RideItem *rideItem); + static QStringList functions(); // return list of functions supported + public slots: QStringList parseFilter(QString query, QStringList *list=0); void clearFilter(); diff --git a/src/DataFilter.y b/src/DataFilter.y index 0c354c133..252348e15 100644 --- a/src/DataFilter.y +++ b/src/DataFilter.y @@ -71,7 +71,7 @@ extern Leaf *root; // root node for parsed statement char function[32]; } -%type symbol value lexpr expr; +%type symbol value lexpr expr parms; %type lop cop bop; %left ADD SUBTRACT DIVIDE MULTIPLY POW @@ -84,7 +84,15 @@ extern Leaf *root; // root node for parsed statement filter: lexpr { root = $1; } ; -lexpr : expr lop expr { $$ = new Leaf(); +parms: lexpr { $$ = new Leaf(); + $$->type = Leaf::Parameters; + $$->fparms << $1; + } + + | parms ',' lexpr { $1->fparms << $3; } + ; + +lexpr : expr lop expr { $$ = new Leaf(); $$->type = Leaf::Logical; $$->lvalue.l = $1; $$->op = $2; @@ -129,6 +137,23 @@ expr : '(' expr ')' { $$ = new Leaf(); | value { $$ = $1; } + /* functions all have zero or more parameters */ + + | symbol '(' parms ')' { /* need to convert symbol to a function */ + $1->type = Leaf::Function; + $1->series = NULL; // not tiz/best + $1->function = *($1->lvalue.n); + $1->fparms = $3->fparms; + } + + | symbol '(' ')' { + /* need to convert symbol to function */ + $1->type = Leaf::Function; + $1->series = NULL; // not tiz/best + $1->function = *($1->lvalue.n); + $1->fparms.clear(); // no parameters! + } + ; cop : EQ diff --git a/src/LTMTool.cpp b/src/LTMTool.cpp index 1690425ae..e8ac0cb8b 100644 --- a/src/LTMTool.cpp +++ b/src/LTMTool.cpp @@ -45,6 +45,9 @@ // PDModel estimate support #include "PDModel.h" +// Filter / formula +#include "DataFilter.h" + LTMTool::LTMTool(Context *context, LTMSettings *settings) : QWidget(context->mainWindow), settings(settings), context(context), active(false), _amFiltered(false) { setStyleSheet("QFrame { FrameStyle = QFrame::NoFrame };" @@ -1736,7 +1739,10 @@ EditMetricDetailDialog::EditMetricDetailDialog(Context *context, LTMTool *ltmToo // get sorted list QStringList names = context->tab->rideNavigator()->logicalHeadings; - // add functions ... + // start with just a list of functions + list = DataFilter::functions(); + + // add special functions (older code needs fixing !) list << "config(cp)"; list << "config(w')"; list << "config(pmax)"; diff --git a/src/SearchBox.cpp b/src/SearchBox.cpp index 99bd1b8e7..e25c6a9a4 100644 --- a/src/SearchBox.cpp +++ b/src/SearchBox.cpp @@ -24,6 +24,7 @@ #include "RideNavigator.h" #include "GcSideBarItem.h" #include "AnalysisSidebar.h" +#include "DataFilter.h" #include #include @@ -147,6 +148,42 @@ SearchBox::configChanged(qint32) QString last; SpecialFields sp; + // start with just a list of functions + list = DataFilter::functions(); + + // add special functions (older code needs fixing !) + list << "config(cp)"; + list << "config(w')"; + list << "config(pmax)"; + list << "config(cv)"; + list << "config(scv)"; + list << "config(height)"; + list << "config(weight)"; + list << "config(lthr)"; + list << "config(maxhr)"; + list << "config(rhr)"; + list << "config(units)"; + list << "const(e)"; + list << "const(pi)"; + list << "ctl"; + list << "tsb"; + list << "atl"; + list << "sb(TSS)"; + list << "lts(TSS)"; + list << "sts(TSS)"; + list << "rr(TSS)"; + list << "tiz(power, 1)"; + list << "tiz(hr, 1)"; + list << "best(power, 3600)"; + list << "best(hr, 3600)"; + list << "best(cadence, 3600)"; + list << "best(speed, 3600)"; + list << "best(torque, 3600)"; + list << "best(np, 3600)"; + list << "best(xpower, 3600)"; + list << "best(vam, 3600)"; + list << "best(wpk, 3600)"; + // get sorted list QStringList names = context->tab->rideNavigator()->logicalHeadings; qSort(names.begin(), names.end(), insensitiveLessThan); @@ -168,6 +205,9 @@ SearchBox::configChanged(qint32) list << name; } + // sort the list + qSort(list.begin(), list.end(), insensitiveLessThan); + // set new list completer->setList(list); }