mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 16:39:57 +00:00
We now have named functions that can be called in the datafilter.
This is primarily to support user defined metrics where we will expect
the user to optionally define a bunch of functions we will call as
part of the ridemetric methods (see design mock up).
.. Datafilters can be defimed in three forms;
* a single line, typically as a filter
e.g. TSS > 100
* a block of code, typically as a formula
e.g. { val <- TSS; val > 100; }
* a program made up of functions, typically as a user metric
e.g. { pass { TSS>100; } main { pass(); } }
.. This example is functionally equivalent to "TSS>250":
{
pass {
# only filter rides with a high TSS
TSS > 250;
}
main {
# call our function to filter rides
pass();
}
}
.. Functions can only be defined within a block
.. Functions must be defined before use
.. A "main" function must be defined as an entry point
into the program if any functions are defined.
474 lines
23 KiB
Plaintext
474 lines
23 KiB
Plaintext
%{
|
|
/*
|
|
* Copyright (c) 2012-2016 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
|
|
*/
|
|
|
|
// This grammar should work with yacc and bison, but has
|
|
// only been tested with bison. In addition, since qmake
|
|
// uses the -p flag to rename all the yy functions to
|
|
// enable multiple grammars in a single executable you
|
|
// should make sure you use the very latest bison since it
|
|
// has been known to be problematic in the past. It is
|
|
// know to work well with bison v2.4.1.
|
|
//
|
|
// To make the grammar readable I have placed the code
|
|
// for each nterm at column 40, this source file is best
|
|
// edited / viewed in an editor which is at least 120
|
|
// columns wide (e.g. vi in xterm of 120x40)
|
|
//
|
|
//
|
|
|
|
#include "DataFilter.h"
|
|
|
|
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
|
|
extern QStringList DataFiltererrors;
|
|
static void DataFiltererror(const char *error) { DataFiltererrors << QString(error);}
|
|
extern int DataFilterparse();
|
|
|
|
extern Leaf *DataFilterroot; // root node for parsed statement
|
|
|
|
%}
|
|
|
|
// Symbol can be meta or metric name
|
|
%token <leaf> SYMBOL
|
|
|
|
// Constants can be a string or a number
|
|
%token <leaf> DF_STRING DF_INTEGER DF_FLOAT
|
|
%token <function> BEST TIZ CONFIG CONST_ DATERANGE
|
|
|
|
// comparative operators
|
|
%token <op> IF_ ELSE_ WHILE
|
|
%token <op> EQ NEQ LT LTE GT GTE ELVIS ASSIGN
|
|
%token <op> ADD SUBTRACT DIVIDE MULTIPLY POW
|
|
%token <op> MATCHES ENDSWITH BEGINSWITH CONTAINS
|
|
%type <op> AND OR;
|
|
|
|
%union {
|
|
Leaf *leaf;
|
|
QList<Leaf*> *comp;
|
|
int op;
|
|
char function[32];
|
|
}
|
|
|
|
%locations
|
|
|
|
%type <leaf> symbol literal lexpr cexpr expr parms block statement expression;
|
|
%type <leaf> simple_statement if_clause while_clause function_def;
|
|
%type <comp> statements
|
|
|
|
%right '?' ':'
|
|
%right '[' ']'
|
|
%right AND OR
|
|
%right EQ NEQ LT LTE GT GTE MATCHES ENDSWITH CONTAINS
|
|
%left ADD SUBTRACT
|
|
%left MULTIPLY DIVIDE
|
|
%right POW
|
|
|
|
%start filter;
|
|
%%
|
|
|
|
/* a one line filter needs to be accounted for as well as a
|
|
complex filter comprised of a block, this is an anomaly
|
|
caused by using the same code for filtering e.g. TSS > 100
|
|
as well as custom metric code involving variables, if and
|
|
while clauses etc
|
|
|
|
As a result the 'filter' needs to explicitly list every
|
|
type of clause that could be used rather than reference
|
|
a 'statement', since we don't want to force the user to
|
|
add a semi-colon to one line filters and formulas. This
|
|
is particularly important since this was all that was
|
|
possible before 3.3
|
|
*/
|
|
|
|
filter:
|
|
|
|
simple_statement { DataFilterroot = $1; }
|
|
| if_clause { DataFilterroot = $1; }
|
|
| while_clause { DataFilterroot = $1; }
|
|
| block { DataFilterroot = $1; }
|
|
;
|
|
|
|
/*
|
|
* A one line statement or a block of code. These are evaluated
|
|
* independently or as part of an if or while clause.
|
|
*/
|
|
expression:
|
|
|
|
statement { $$ = $1; }
|
|
| block { $$ = $1; }
|
|
;
|
|
|
|
/*
|
|
* A block of code within braces.
|
|
*/
|
|
block:
|
|
|
|
'{' statements '}' { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Compound;
|
|
$$->lvalue.b = $2;
|
|
}
|
|
;
|
|
|
|
/*
|
|
* A set of lines of code
|
|
*/
|
|
statements:
|
|
|
|
statement { $$ = new QList<Leaf*>(); $$->append($1); }
|
|
| statements statement { $$->append($2); }
|
|
;
|
|
|
|
/*
|
|
* A single line of code, terminated with ';' if needed.
|
|
*/
|
|
statement:
|
|
|
|
simple_statement ';'
|
|
| if_clause
|
|
| while_clause
|
|
| function_def
|
|
;
|
|
|
|
/*
|
|
* A single line of code unterminated to use as a one-line filter
|
|
*/
|
|
simple_statement:
|
|
|
|
lexpr { $$ = $1; }
|
|
| symbol ASSIGN lexpr {
|
|
$$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
;
|
|
|
|
/*
|
|
* if then else clause AST is same as a ternary
|
|
* dangling else handled by bison default shift
|
|
*/
|
|
if_clause:
|
|
|
|
IF_ '(' lexpr ')' expression { $$ = new Leaf(@1.first_column, @5.last_column);
|
|
$$->type = Leaf::Conditional;
|
|
$$->op = IF_;
|
|
$$->lvalue.l = $5;
|
|
$$->rvalue.l = NULL;
|
|
$$->cond.l = $3;
|
|
}
|
|
| IF_ '(' lexpr ')' expression
|
|
ELSE_ expression { $$ = new Leaf(@1.first_column, @5.last_column);
|
|
$$->type = Leaf::Conditional;
|
|
$$->op = IF_;
|
|
$$->lvalue.l = $5;
|
|
$$->rvalue.l = $7;
|
|
$$->cond.l = $3;
|
|
}
|
|
|
|
;
|
|
|
|
/*
|
|
* While clause, handled similarly to if/else
|
|
*/
|
|
while_clause:
|
|
|
|
WHILE '(' lexpr ')' expression { $$ = new Leaf(@1.first_column, @5.last_column);
|
|
$$->type = Leaf::Conditional;
|
|
$$->op = WHILE;
|
|
$$->lvalue.l = $5;
|
|
$$->rvalue.l = NULL;
|
|
$$->cond.l = $3;
|
|
}
|
|
;
|
|
|
|
/*
|
|
* A user defined function which may be a function that is called by
|
|
* the user metric framework; e.g. init, sample, value, count etc
|
|
*/
|
|
|
|
function_def:
|
|
|
|
symbol block { $$ = $2;
|
|
$$->function = *($1->lvalue.n);
|
|
}
|
|
;
|
|
|
|
/*
|
|
* A parameter list, as passed to a function
|
|
*/
|
|
parms:
|
|
|
|
lexpr { $$ = new Leaf(@1.first_column, @1.last_column);
|
|
$$->type = Leaf::Parameters;
|
|
$$->fparms << $1;
|
|
}
|
|
| parms ',' lexpr { $1->fparms << $3; }
|
|
;
|
|
|
|
/*
|
|
* A logical expression that is ultimately used to evaluate to bool
|
|
*/
|
|
lexpr:
|
|
|
|
expr { $$ = $1; }
|
|
| cexpr { $$ = $1; }
|
|
| '(' lexpr ')' { $$ = new Leaf(@2.first_column, @2.last_column);
|
|
$$->type = Leaf::Logical;
|
|
$$->lvalue.l = $2;
|
|
$$->op = 0;
|
|
}
|
|
| lexpr '?' lexpr ':' lexpr { $$ = new Leaf(@1.first_column, @5.last_column);
|
|
$$->type = Leaf::Conditional;
|
|
$$->op = 0; // zero for tertiary
|
|
$$->lvalue.l = $3;
|
|
$$->rvalue.l = $5;
|
|
$$->cond.l = $1;
|
|
}
|
|
| lexpr '[' lexpr ':' lexpr ']' { $$ = new Leaf(@1.first_column, @6.last_column);
|
|
$$->type = Leaf::Vector;
|
|
$$->lvalue.l = $1;
|
|
$$->fparms << $3;
|
|
$$->fparms << $5;
|
|
$$->op = 0;
|
|
}
|
|
| '!' lexpr %prec OR { $$ = new Leaf(@1.first_column, @2.last_column);
|
|
$$->type = Leaf::UnaryOperation;
|
|
$$->lvalue.l = $2;
|
|
$$->op = '!';
|
|
$$->rvalue.l = NULL;
|
|
}
|
|
| lexpr OR lexpr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Logical;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| lexpr AND lexpr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Logical;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
;
|
|
|
|
/*
|
|
* A compare expression evaluates to bool
|
|
*/
|
|
cexpr:
|
|
|
|
expr EQ expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr NEQ expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr LT expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
|
|
| expr LTE expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr GT expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
|
|
| expr GTE expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr ELVIS expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr MATCHES expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr ENDSWITH expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr BEGINSWITH expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr CONTAINS expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::Operation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
;
|
|
|
|
/*
|
|
* An arithmetic expression, includes function calls and similar
|
|
*/
|
|
expr:
|
|
|
|
expr SUBTRACT expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::BinaryOperation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr ADD expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::BinaryOperation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr DIVIDE expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::BinaryOperation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr MULTIPLY expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::BinaryOperation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| expr POW expr { $$ = new Leaf(@1.first_column, @3.last_column);
|
|
$$->type = Leaf::BinaryOperation;
|
|
$$->lvalue.l = $1;
|
|
$$->op = $2;
|
|
$$->rvalue.l = $3;
|
|
}
|
|
| SUBTRACT expr %prec MULTIPLY { $$ = new Leaf(@1.first_column, @2.last_column);
|
|
$$->type = Leaf::UnaryOperation;
|
|
$$->lvalue.l = $2;
|
|
$$->op = $1;
|
|
$$->rvalue.l = NULL;
|
|
}
|
|
| BEST '(' symbol ',' lexpr ')' { $$ = new Leaf(@1.first_column, @6.last_column); $$->type = Leaf::Function;
|
|
$$->function = QString($1);
|
|
$$->series = $3;
|
|
$$->lvalue.l = $5;
|
|
}
|
|
| TIZ '(' symbol ',' lexpr ')' { $$ = new Leaf(@1.first_column, @6.last_column); $$->type = Leaf::Function;
|
|
$$->function = QString($1);
|
|
$$->series = $3;
|
|
$$->lvalue.l = $5;
|
|
}
|
|
| CONFIG '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
|
|
$$->function = QString($1);
|
|
$$->series = $3;
|
|
$$->lvalue.l = NULL;
|
|
}
|
|
| CONST_ '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
|
|
$$->function = QString($1);
|
|
$$->series = $3;
|
|
$$->lvalue.l = NULL;
|
|
}
|
|
| DATERANGE '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
|
|
$$->function = QString($1);
|
|
$$->series = $3;
|
|
$$->lvalue.l = NULL;
|
|
}
|
|
/* functions all have zero or more parameters */
|
|
| symbol '(' parms ')' { /* need to convert symbol to a function */
|
|
$1->leng = @4.last_column;
|
|
$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!
|
|
}
|
|
| '(' expr ')' { $$ = new Leaf(@2.first_column, @2.last_column);
|
|
$$->type = Leaf::Logical;
|
|
$$->lvalue.l = $2;
|
|
$$->op = 0;
|
|
}
|
|
| symbol { $$ = $1; }
|
|
| literal { $$ = $1; }
|
|
;
|
|
|
|
/*
|
|
* A built in or user defined symbol
|
|
*/
|
|
symbol:
|
|
|
|
SYMBOL { $$ = new Leaf(@1.first_column, @1.last_column);
|
|
$$->type = Leaf::Symbol;
|
|
if (QString(DataFiltertext) == "BikeScore") $$->lvalue.n = new QString("BikeScore™");
|
|
else $$->lvalue.n = new QString(DataFiltertext);
|
|
}
|
|
;
|
|
|
|
/*
|
|
* Any string or numeric literal
|
|
*/
|
|
literal:
|
|
|
|
DF_STRING { $$ = new Leaf(@1.first_column, @1.last_column);
|
|
$$->type = Leaf::String;
|
|
QString s2(DataFiltertext);
|
|
$$->lvalue.s = new QString(s2.mid(1,s2.length()-2));
|
|
}
|
|
| DF_FLOAT { $$ = new Leaf(@1.first_column, @1.last_column);
|
|
$$->type = Leaf::Float;
|
|
$$->lvalue.f = QString(DataFiltertext).toFloat();
|
|
}
|
|
| DF_INTEGER { $$ = new Leaf(@1.first_column, @1.last_column);
|
|
$$->type = Leaf::Integer;
|
|
$$->lvalue.i = QString(DataFiltertext).toInt();
|
|
}
|
|
;
|
|
|
|
%%
|
|
|