mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 17:09:56 +00:00
Fixes the crash but the eval code is a bit of a fuck up. It was evaluating children instead of using recursion, and now we have lots more types and expressions its all a bit of a mess. Will need to rewrite to use recursion and it will be a lot simpler!
570 lines
17 KiB
C++
570 lines
17 KiB
C++
/*
|
|
* Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "DataFilter.h"
|
|
#include "Context.h"
|
|
#include "Context.h"
|
|
#include "Athlete.h"
|
|
#include "RideNavigator.h"
|
|
#include <QDebug>
|
|
|
|
#include "DataFilter_yacc.h"
|
|
|
|
// LEXER VARIABLES WE INTERACT WITH
|
|
// Standard yacc/lex variables / functions
|
|
extern int DataFilterlex(); // the lexer aka yylex()
|
|
extern char *DataFiltertext; // set by the lexer aka yytext
|
|
|
|
extern void DataFilter_setString(QString);
|
|
extern void DataFilter_clearString();
|
|
|
|
// PARSER STATE VARIABLES
|
|
QStringList DataFiltererrors;
|
|
extern int DataFilterparse();
|
|
|
|
Leaf *root; // root node for parsed statement
|
|
|
|
void Leaf::print(Leaf *leaf, int level)
|
|
{
|
|
qDebug()<<"LEVEL"<<level;
|
|
switch(leaf->type) {
|
|
case Leaf::Float : qDebug()<<"float"<<leaf->lvalue.f; break;
|
|
case Leaf::Integer : qDebug()<<"integer"<<leaf->lvalue.i; break;
|
|
case Leaf::String : qDebug()<<"string"<<*leaf->lvalue.s; break;
|
|
case Leaf::Symbol : qDebug()<<"symbol"<<*leaf->lvalue.n; break;
|
|
case Leaf::Logical : qDebug()<<"lop"<<leaf->op;
|
|
leaf->print(leaf->lvalue.l, level+1);
|
|
leaf->print(leaf->rvalue.l, level+1);
|
|
break;
|
|
case Leaf::Operation : qDebug()<<"cop"<<leaf->op;
|
|
leaf->print(leaf->lvalue.l, level+1);
|
|
leaf->print(leaf->rvalue.l, level+1);
|
|
break;
|
|
case Leaf::BinaryOperation : qDebug()<<"bop"<<leaf->op;
|
|
leaf->print(leaf->lvalue.l, level+1);
|
|
leaf->print(leaf->rvalue.l, level+1);
|
|
break;
|
|
case Leaf::Function : qDebug()<<"function"<<leaf->function<<"series="<<*(leaf->series->lvalue.n);
|
|
leaf->print(leaf->lvalue.l, level+1);
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
bool Leaf::isNumber(DataFilter *df, Leaf *leaf)
|
|
{
|
|
switch(leaf->type) {
|
|
case Leaf::Float : return true;
|
|
case Leaf::Integer : return true;
|
|
case Leaf::String : return false;
|
|
case Leaf::Symbol : return df->lookupType.value(*(leaf->lvalue.n), false);
|
|
case Leaf::Logical : return true; // not possible!
|
|
case Leaf::Operation : return true;
|
|
case Leaf::BinaryOperation : return true;
|
|
case Leaf::Function : return true;
|
|
default:
|
|
return false;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
void Leaf::clear(Leaf *leaf)
|
|
{
|
|
#if 0 // memory leak!!!
|
|
switch(leaf->type) {
|
|
case Leaf::String : delete leaf->lvalue.s; break;
|
|
case Leaf::Symbol : delete leaf->lvalue.n; break;
|
|
case Leaf::Logical :
|
|
case Leaf::BinaryOperation :
|
|
case Leaf::Operation : clear(leaf->lvalue.l);
|
|
clear(leaf->rvalue.l);
|
|
delete(leaf->lvalue.l);
|
|
delete(leaf->rvalue.l);
|
|
break;
|
|
case Leaf::Function : clear(leaf->lvalue.l);
|
|
delete(leaf->lvalue.l);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Leaf::validateFilter(DataFilter *df, Leaf *leaf)
|
|
{
|
|
switch(leaf->type) {
|
|
case Leaf::Symbol :
|
|
{
|
|
// are the symbols correct?
|
|
// if so set the type to meta or metric
|
|
// and save the technical name used to do
|
|
// a lookup at execution time
|
|
QString lookup = df->lookupMap.value(*(leaf->lvalue.n), "");
|
|
if (lookup == "") {
|
|
DataFiltererrors << QString("%1 is unknown").arg(*(leaf->lvalue.n));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Leaf::Function :
|
|
{
|
|
// is the symbol valid?
|
|
QRegExp bestValidSymbols("^(power|hr|cadence|speed|torque|vam|xpower|np|wpk)$", Qt::CaseInsensitive);
|
|
QRegExp tizValidSymbols("^(power|hr)$", Qt::CaseInsensitive);
|
|
QString symbol = *(leaf->series->lvalue.n);
|
|
|
|
if (leaf->function == "best" && !bestValidSymbols.exactMatch(symbol))
|
|
DataFiltererrors << QString("invalid data series for best(): %1").arg(symbol);
|
|
|
|
if (leaf->function == "tiz" && !tizValidSymbols.exactMatch(symbol))
|
|
DataFiltererrors << QString("invalid data series for tiz(): %1").arg(symbol);
|
|
}
|
|
break;
|
|
|
|
case Leaf::BinaryOperation :
|
|
case Leaf::Operation :
|
|
{
|
|
// first lets make sure the lhs and rhs are of the same type
|
|
bool lhsType = Leaf::isNumber(df, leaf->lvalue.l);
|
|
bool rhsType = Leaf::isNumber(df, leaf->rvalue.l);
|
|
if (lhsType != rhsType) {
|
|
DataFiltererrors << QString("comparing strings with numbers");
|
|
}
|
|
|
|
// what about using string operations on a lhs/rhs that
|
|
// are numeric?
|
|
if ((lhsType || rhsType) && leaf->op >= MATCHES && leaf->op <= CONTAINS) {
|
|
DataFiltererrors << "using a string operations with a number";
|
|
}
|
|
|
|
validateFilter(df, leaf->lvalue.l);
|
|
validateFilter(df, leaf->rvalue.l);
|
|
}
|
|
break;
|
|
|
|
case Leaf::Logical :
|
|
{
|
|
validateFilter(df, leaf->lvalue.l);
|
|
if (leaf->op) validateFilter(df, leaf->rvalue.l);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
DataFilter::DataFilter(QObject *parent, Context *context) : QObject(parent), context(context), treeRoot(NULL)
|
|
{
|
|
configUpdate();
|
|
connect(context, SIGNAL(configChanged()), this, SLOT(configUpdate()));
|
|
}
|
|
|
|
QStringList DataFilter::parseFilter(QString query)
|
|
{
|
|
//DataFilterdebug = 2; // no debug -- needs bison -t in src.pro
|
|
root = NULL;
|
|
|
|
// if something was left behind clear it up now
|
|
clearFilter();
|
|
|
|
// Parse from string
|
|
DataFiltererrors.clear(); // clear out old errors
|
|
DataFilter_setString(query);
|
|
DataFilterparse();
|
|
DataFilter_clearString();
|
|
|
|
// save away the results
|
|
treeRoot = root;
|
|
|
|
// if it passed syntax lets check semantics
|
|
if (treeRoot && DataFiltererrors.count() == 0) treeRoot->validateFilter(this, treeRoot);
|
|
|
|
// ok, did it pass all tests?
|
|
if (!treeRoot || DataFiltererrors.count() > 0) { // nope
|
|
|
|
// no errors just failed to finish
|
|
if (!treeRoot) DataFiltererrors << "malformed expression.";
|
|
|
|
// Bzzzt, malformed
|
|
emit parseBad(DataFiltererrors);
|
|
clearFilter();
|
|
|
|
} else { // yep! .. we have a winner!
|
|
|
|
// successfuly parsed, lets check semantics
|
|
//treeRoot->print(treeRoot);
|
|
emit parseGood();
|
|
|
|
// get all fields...
|
|
QList<SummaryMetrics> allRides = context->athlete->metricDB->getAllMetricsFor(QDateTime(), QDateTime());
|
|
|
|
filenames.clear();
|
|
|
|
for (int i=0; i<allRides.count(); i++) {
|
|
|
|
// evaluate each ride...
|
|
double result = treeRoot->eval(this, treeRoot, allRides.at(i));
|
|
if (result) {
|
|
filenames << allRides.at(i).getFileName();
|
|
}
|
|
}
|
|
emit results(filenames);
|
|
}
|
|
|
|
errors = DataFiltererrors;
|
|
return errors;
|
|
}
|
|
|
|
void DataFilter::clearFilter()
|
|
{
|
|
if (treeRoot) {
|
|
treeRoot->clear(treeRoot);
|
|
treeRoot = NULL;
|
|
}
|
|
}
|
|
|
|
void DataFilter::configUpdate()
|
|
{
|
|
lookupMap.clear();
|
|
lookupType.clear();
|
|
|
|
// create lookup map from 'friendly name' to name used in smmaryMetrics
|
|
// to enable a quick lookup && the lookup for the field type (number, text)
|
|
const RideMetricFactory &factory = RideMetricFactory::instance();
|
|
for (int i=0; i<factory.metricCount(); i++) {
|
|
QString symbol = factory.metricName(i);
|
|
QString name = factory.rideMetric(symbol)->name();
|
|
lookupMap.insert(name.replace(" ","_"), symbol);
|
|
lookupType.insert(name.replace(" ","_"), true);
|
|
}
|
|
|
|
// now add the ride metadata fields -- should be the same generally
|
|
foreach(FieldDefinition field, context->athlete->rideMetadata()->getFields()) {
|
|
QString underscored = field.name;
|
|
if (!context->specialFields.isMetric(underscored)) {
|
|
lookupMap.insert(underscored.replace(" ","_"), field.name);
|
|
lookupType.insert(underscored.replace(" ","_"), (field.type > 2)); // true if is number
|
|
}
|
|
}
|
|
}
|
|
|
|
double Leaf::eval(DataFilter *df, Leaf *leaf, SummaryMetrics m)
|
|
{
|
|
switch(leaf->type) {
|
|
|
|
case Leaf::Logical :
|
|
{
|
|
switch (leaf->op) {
|
|
case AND :
|
|
return (eval(df, leaf->lvalue.l, m) && eval(df, leaf->rvalue.l, m));
|
|
|
|
case OR :
|
|
return (eval(df, leaf->lvalue.l, m) || eval(df, leaf->rvalue.l, m));
|
|
|
|
default : // parenthesis
|
|
return (eval(df, leaf->lvalue.l, m));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Leaf::Function :
|
|
{
|
|
double duration;
|
|
|
|
// GET LHS VALUE
|
|
switch (leaf->lvalue.l->type) {
|
|
|
|
default:
|
|
case Leaf::Function :
|
|
{
|
|
duration = eval(df, leaf->lvalue.l, m); // duration
|
|
}
|
|
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;
|
|
}
|
|
//qDebug()<<"best for series"<<*(leaf->series->lvalue.n)<<duration<<"returning 100";
|
|
|
|
//XXX!!! we must now call the real function to get the tiz/best for duration...
|
|
return 100;
|
|
}
|
|
break;
|
|
|
|
case Leaf::BinaryOperation :
|
|
case Leaf::Operation :
|
|
{
|
|
double lhsdouble=0.00, rhsdouble=0.00;
|
|
QString lhsstring, rhsstring;
|
|
bool lhsisNumber=false, rhsisNumber=false;
|
|
|
|
// GET LHS VALUE
|
|
switch (leaf->lvalue.l->type) {
|
|
|
|
default:
|
|
case Leaf::Function :
|
|
{
|
|
lhsdouble = eval(df, leaf->lvalue.l, m); // duration
|
|
lhsisNumber=true;
|
|
}
|
|
break;
|
|
|
|
case Leaf::Symbol :
|
|
{
|
|
QString rename;
|
|
// get symbol value
|
|
if ((lhsisNumber = df->lookupType.value(*(leaf->lvalue.l->lvalue.n))) == true) {
|
|
// numeric
|
|
lhsdouble = m.getForSymbol(rename=df->lookupMap.value(*(leaf->lvalue.l->lvalue.n),""));
|
|
//qDebug()<<"symbol" << *(leaf->lvalue.l->lvalue.n) << "is" << lhsdouble << "via" << rename;
|
|
} else {
|
|
// string
|
|
lhsstring = m.getText(rename=df->lookupMap.value(*(leaf->lvalue.l->lvalue.n),""), "");
|
|
//qDebug()<<"symbol" << *(leaf->lvalue.l->lvalue.n) << "is" << lhsstring << "via" << rename;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Leaf::Float :
|
|
lhsisNumber = true;
|
|
lhsdouble = leaf->lvalue.l->lvalue.f;
|
|
break;
|
|
|
|
case Leaf::Integer :
|
|
lhsisNumber = true;
|
|
lhsdouble = leaf->lvalue.l->lvalue.i;
|
|
break;
|
|
|
|
case Leaf::String :
|
|
lhsisNumber = false;
|
|
lhsstring = *(leaf->lvalue.l->lvalue.s);
|
|
break;
|
|
|
|
break;
|
|
}
|
|
|
|
// GET RHS VALUE -- BUT NOT FOR FUNCTIONS
|
|
switch (leaf->rvalue.l->type) {
|
|
|
|
default:
|
|
case Leaf::Function :
|
|
{
|
|
rhsdouble = eval(df, leaf->rvalue.l, m);
|
|
rhsisNumber=true;
|
|
}
|
|
break;
|
|
case Leaf::Symbol :
|
|
{
|
|
QString rename;
|
|
// get symbol value
|
|
if ((rhsisNumber=df->lookupType.value(*(leaf->rvalue.l->lvalue.n))) == true) {
|
|
// numeric
|
|
rhsdouble = m.getForSymbol(rename=df->lookupMap.value(*(leaf->rvalue.l->lvalue.n),""));
|
|
//qDebug()<<"symbol" << *(leaf->rvalue.l->lvalue.n) << "is" << rhsdouble << "via" << rename;
|
|
} else {
|
|
// string
|
|
rhsstring = m.getText(rename=df->lookupMap.value(*(leaf->rvalue.l->lvalue.n),""), "notfound");
|
|
//qDebug()<<"symbol" << *(leaf->rvalue.l->lvalue.n) << "is" << rhsstring << "via" << rename;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Leaf::Float :
|
|
rhsisNumber = true;
|
|
rhsdouble = leaf->rvalue.l->lvalue.f;
|
|
break;
|
|
|
|
case Leaf::Integer :
|
|
rhsisNumber = true;
|
|
rhsdouble = leaf->rvalue.l->lvalue.i;
|
|
break;
|
|
|
|
case Leaf::String :
|
|
rhsisNumber = false;
|
|
rhsstring = *(leaf->rvalue.l->lvalue.s);
|
|
break;
|
|
|
|
break;
|
|
}
|
|
|
|
// NOW PERFORM OPERATION
|
|
//qDebug()<<"lhs="<<lhsdouble<<"rhs="<<rhsdouble;
|
|
switch (leaf->op) {
|
|
|
|
case ADD:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble + rhsdouble;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SUBTRACT:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble - rhsdouble;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DIVIDE:
|
|
{
|
|
if (lhsisNumber && rhsdouble) { // avoid divide by zero
|
|
return lhsdouble / rhsdouble;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MULTIPLY:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble * rhsdouble;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case POW:
|
|
{
|
|
if (lhsisNumber && rhsdouble) {
|
|
return pow(lhsdouble,rhsdouble);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EQ:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble == rhsdouble;
|
|
} else {
|
|
return lhsstring == rhsstring;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NEQ:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble != rhsdouble;
|
|
} else {
|
|
return lhsstring != rhsstring;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LT:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble < rhsdouble;
|
|
} else {
|
|
return lhsstring < rhsstring;
|
|
}
|
|
}
|
|
break;
|
|
case LTE:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble <= rhsdouble;
|
|
} else {
|
|
return lhsstring <= rhsstring;
|
|
}
|
|
}
|
|
break;
|
|
case GT:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble > rhsdouble;
|
|
} else {
|
|
return lhsstring > rhsstring;
|
|
}
|
|
}
|
|
break;
|
|
case GTE:
|
|
{
|
|
if (lhsisNumber) {
|
|
return lhsdouble >= rhsdouble;
|
|
} else {
|
|
return lhsstring >= rhsstring;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MATCHES:
|
|
return QRegExp(rhsstring).exactMatch(lhsstring);
|
|
break;
|
|
|
|
case ENDSWITH:
|
|
return lhsstring.endsWith(rhsstring);
|
|
break;
|
|
|
|
case BEGINSWITH:
|
|
return lhsstring.startsWith(rhsstring);
|
|
break;
|
|
|
|
case CONTAINS:
|
|
return lhsstring.contains(rhsstring) ? true : false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: // we don't need to evaluate any lower - they are leaf nodes handled above
|
|
break;
|
|
}
|
|
return false;
|
|
}
|