mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-31 15:06:26 +00:00
wip
This commit is contained in:
parent
dfcb8dfd65
commit
e25b2e7f05
3 changed files with 492 additions and 253 deletions
|
@ -223,13 +223,6 @@ func parsePipe(lex *lexer) (pipe, error) {
|
|||
default:
|
||||
lexState := lex.backupState()
|
||||
|
||||
// Try parsing stats pipe without 'stats' keyword
|
||||
ps, err := parsePipeStats(lex, false)
|
||||
if err == nil {
|
||||
return ps, nil
|
||||
}
|
||||
lex.restoreState(lexState)
|
||||
|
||||
// Try parsing math pipe without 'math' keyword
|
||||
pm, err := parsePipeMath(lex, false)
|
||||
if err == nil {
|
||||
|
@ -237,6 +230,13 @@ func parsePipe(lex *lexer) (pipe, error) {
|
|||
}
|
||||
lex.restoreState(lexState)
|
||||
|
||||
// Try parsing stats pipe without 'stats' keyword
|
||||
ps, err := parsePipeStats(lex, false)
|
||||
if err == nil {
|
||||
return ps, nil
|
||||
}
|
||||
lex.restoreState(lexState)
|
||||
|
||||
// Try parsing filter pipe without 'filter' keyword
|
||||
pf, err := parsePipeFilter(lex, false)
|
||||
if err == nil {
|
||||
|
|
|
@ -19,11 +19,42 @@ type pipeMath struct {
|
|||
}
|
||||
|
||||
type mathEntry struct {
|
||||
resultField string
|
||||
be *mathBinaryExpr
|
||||
neededFields []string
|
||||
// The calculated expr result is stored in resultField.
|
||||
resultField string
|
||||
|
||||
// expr is the expression to calculate.
|
||||
expr *mathExpr
|
||||
}
|
||||
|
||||
type mathExpr struct {
|
||||
// if isConst is set, then the given mathExpr returns the given constValue.
|
||||
isConst bool
|
||||
constValue float64
|
||||
|
||||
// constValueStr is the original string representation of constValue.
|
||||
//
|
||||
// It is used in String() method for returning the original representation of the given constValue.
|
||||
constValueStr string
|
||||
|
||||
// if fieldName isn't empty, then the given mathExpr fetches numeric values from the given fieldName.
|
||||
fieldName string
|
||||
|
||||
// args are args for the given mathExpr.
|
||||
args []*mathExpr
|
||||
|
||||
// op is the operation name (aka function name) for the given mathExpr.
|
||||
op string
|
||||
|
||||
// f is the function for calculating results for the given mathExpr.
|
||||
f mathFunc
|
||||
|
||||
// whether the mathExpr was wrapped in parens.
|
||||
wrappedInParens bool
|
||||
}
|
||||
|
||||
// mathFunc must fill result with calculated results based on the given args.
|
||||
type mathFunc func(result []float64, args [][]float64)
|
||||
|
||||
func (pm *pipeMath) String() string {
|
||||
s := "math"
|
||||
a := make([]string, len(pm.entries))
|
||||
|
@ -35,7 +66,105 @@ func (pm *pipeMath) String() string {
|
|||
}
|
||||
|
||||
func (me *mathEntry) String() string {
|
||||
return quoteTokenIfNeeded(me.resultField) + " = " + me.be.String()
|
||||
s := me.expr.String()
|
||||
if isMathBinaryOp(me.expr.op) {
|
||||
s = "(" + s + ")"
|
||||
}
|
||||
s += " as " + quoteTokenIfNeeded(me.resultField)
|
||||
return s
|
||||
}
|
||||
|
||||
func (me *mathExpr) String() string {
|
||||
if me.isConst {
|
||||
return me.constValueStr
|
||||
}
|
||||
if me.fieldName != "" {
|
||||
return quoteTokenIfNeeded(me.fieldName)
|
||||
}
|
||||
|
||||
args := me.args
|
||||
if isMathBinaryOp(me.op) {
|
||||
opPriority := getMathBinaryOpPriority(me.op)
|
||||
left := me.args[0]
|
||||
right := me.args[1]
|
||||
leftStr := left.String()
|
||||
rightStr := right.String()
|
||||
if isMathBinaryOp(left.op) && getMathBinaryOpPriority(left.op) > opPriority {
|
||||
leftStr = "(" + leftStr + ")"
|
||||
}
|
||||
if isMathBinaryOp(right.op) && getMathBinaryOpPriority(right.op) > opPriority {
|
||||
rightStr = "(" + rightStr + ")"
|
||||
}
|
||||
return fmt.Sprintf("%s %s %s", leftStr, me.op, rightStr)
|
||||
}
|
||||
|
||||
if me.op == "unary_minus" {
|
||||
argStr := args[0].String()
|
||||
if isMathBinaryOp(args[0].op) {
|
||||
argStr = "(" + argStr + ")"
|
||||
}
|
||||
return "-" + argStr
|
||||
}
|
||||
|
||||
a := make([]string, len(args))
|
||||
for i, arg := range args {
|
||||
a[i] = arg.String()
|
||||
}
|
||||
argsStr := strings.Join(a, ", ")
|
||||
return fmt.Sprintf("%s(%s)", me.op, argsStr)
|
||||
}
|
||||
|
||||
func isMathBinaryOp(op string) bool {
|
||||
_, ok := mathBinaryOps[op]
|
||||
return ok
|
||||
}
|
||||
|
||||
func getMathBinaryOpPriority(op string) int {
|
||||
bo, ok := mathBinaryOps[op]
|
||||
if !ok {
|
||||
logger.Panicf("BUG: unexpected binary op: %q", op)
|
||||
}
|
||||
return bo.priority
|
||||
}
|
||||
|
||||
func getMathFuncForBinaryOp(op string) (mathFunc, error) {
|
||||
bo, ok := mathBinaryOps[op]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported binary operation: %q", op)
|
||||
}
|
||||
return bo.f, nil
|
||||
}
|
||||
|
||||
var mathBinaryOps = map[string]mathBinaryOp{
|
||||
"^": {
|
||||
priority: 1,
|
||||
f: mathFuncPow,
|
||||
},
|
||||
"*": {
|
||||
priority: 2,
|
||||
f: mathFuncMul,
|
||||
},
|
||||
"/": {
|
||||
priority: 2,
|
||||
f: mathFuncDiv,
|
||||
},
|
||||
"%": {
|
||||
priority: 2,
|
||||
f: mathFuncMod,
|
||||
},
|
||||
"+": {
|
||||
priority: 3,
|
||||
f: mathFuncPlus,
|
||||
},
|
||||
"-": {
|
||||
priority: 3,
|
||||
f: mathFuncMinus,
|
||||
},
|
||||
}
|
||||
|
||||
type mathBinaryOp struct {
|
||||
priority int
|
||||
f mathFunc
|
||||
}
|
||||
|
||||
func (pm *pipeMath) updateNeededFields(neededFields, unneededFields fieldsSet) {
|
||||
|
@ -44,17 +173,40 @@ func (pm *pipeMath) updateNeededFields(neededFields, unneededFields fieldsSet) {
|
|||
if neededFields.contains("*") {
|
||||
if !unneededFields.contains(e.resultField) {
|
||||
unneededFields.add(e.resultField)
|
||||
unneededFields.removeFields(e.neededFields)
|
||||
|
||||
entryFields := e.getNeededFields()
|
||||
unneededFields.removeFields(entryFields)
|
||||
}
|
||||
} else {
|
||||
if neededFields.contains(e.resultField) {
|
||||
neededFields.remove(e.resultField)
|
||||
neededFields.addFields(e.neededFields)
|
||||
|
||||
entryFields := e.getNeededFields()
|
||||
neededFields.addFields(entryFields)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (me *mathEntry) getNeededFields() []string {
|
||||
neededFields := newFieldsSet()
|
||||
me.expr.updateNeededFields(neededFields)
|
||||
return neededFields.getAll()
|
||||
}
|
||||
|
||||
func (me *mathExpr) updateNeededFields(neededFields fieldsSet) {
|
||||
if me.isConst {
|
||||
return
|
||||
}
|
||||
if me.fieldName != "" {
|
||||
neededFields.add(me.fieldName)
|
||||
return
|
||||
}
|
||||
for _, arg := range me.args {
|
||||
arg.updateNeededFields(neededFields)
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *pipeMath) optimize() {
|
||||
// nothing to do
|
||||
}
|
||||
|
@ -92,15 +244,25 @@ type pipeMathProcessorShard struct {
|
|||
}
|
||||
|
||||
type pipeMathProcessorShardNopad struct {
|
||||
a arena
|
||||
// a holds all the data for rcs.
|
||||
a arena
|
||||
|
||||
// rcs is used for storing calculated results before they are written to ppNext.
|
||||
rcs []resultColumn
|
||||
|
||||
// rs is storage for temporary results
|
||||
rs [][]float64
|
||||
|
||||
// rsBuf is backing storage for rs slices
|
||||
rsBuf []float64
|
||||
}
|
||||
|
||||
func (shard *pipeMathProcessorShard) executeMathEntry(e *mathEntry, rc *resultColumn, br *blockResult) {
|
||||
clear(shard.rs)
|
||||
shard.rs = shard.rs[:0]
|
||||
shard.executeBinaryExpr(e.be, br)
|
||||
shard.rsBuf = shard.rsBuf[:0]
|
||||
|
||||
shard.executeExpr(e.expr, br)
|
||||
r := shard.rs[0]
|
||||
|
||||
b := shard.a.b
|
||||
|
@ -113,20 +275,22 @@ func (shard *pipeMathProcessorShard) executeMathEntry(e *mathEntry, rc *resultCo
|
|||
shard.a.b = b
|
||||
}
|
||||
|
||||
func (shard *pipeMathProcessorShard) executeBinaryExpr(expr *mathBinaryExpr, br *blockResult) {
|
||||
func (shard *pipeMathProcessorShard) executeExpr(me *mathExpr, br *blockResult) {
|
||||
rIdx := len(shard.rs)
|
||||
shard.rs = slicesutil.SetLength(shard.rs, len(shard.rs)+1)
|
||||
shard.rs[rIdx] = slicesutil.SetLength(shard.rs[rIdx], len(br.timestamps))
|
||||
|
||||
if expr.isConst {
|
||||
shard.rsBuf = slicesutil.SetLength(shard.rsBuf, len(shard.rsBuf)+len(br.timestamps))
|
||||
shard.rs[rIdx] = shard.rsBuf[len(shard.rsBuf)-len(br.timestamps):]
|
||||
|
||||
if me.isConst {
|
||||
r := shard.rs[rIdx]
|
||||
for i := range br.timestamps {
|
||||
r[i] = expr.constValue
|
||||
r[i] = me.constValue
|
||||
}
|
||||
return
|
||||
}
|
||||
if expr.fieldName != "" {
|
||||
c := br.getColumnByName(expr.fieldName)
|
||||
if me.fieldName != "" {
|
||||
c := br.getColumnByName(me.fieldName)
|
||||
values := c.getValues(br)
|
||||
r := shard.rs[rIdx]
|
||||
var f float64
|
||||
|
@ -143,19 +307,15 @@ func (shard *pipeMathProcessorShard) executeBinaryExpr(expr *mathBinaryExpr, br
|
|||
return
|
||||
}
|
||||
|
||||
shard.executeBinaryExpr(expr.left, br)
|
||||
shard.executeBinaryExpr(expr.right, br)
|
||||
|
||||
r := shard.rs[rIdx]
|
||||
rLeft := shard.rs[rIdx+1]
|
||||
rRight := shard.rs[rIdx+2]
|
||||
|
||||
mathFunc := expr.mathFunc
|
||||
for i := range r {
|
||||
r[i] = mathFunc(rLeft[i], rRight[i])
|
||||
rsBufLen := len(shard.rsBuf)
|
||||
for _, arg := range me.args {
|
||||
shard.executeExpr(arg, br)
|
||||
}
|
||||
|
||||
me.f(shard.rs[rIdx], shard.rs[rIdx+1:])
|
||||
|
||||
shard.rs = shard.rs[:rIdx+1]
|
||||
shard.rsBuf = shard.rsBuf[:rsBufLen]
|
||||
}
|
||||
|
||||
func (pmp *pipeMathProcessor) writeBlock(workerID uint, br *blockResult) {
|
||||
|
@ -215,271 +375,262 @@ func parsePipeMath(lex *lexer, needMathKeyword bool) (*pipeMath, error) {
|
|||
}
|
||||
return pm, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected token after 'math' expression [%s]: %q; expacting ',', '|' or ')'", mes[len(mes)-1], lex.token)
|
||||
return nil, fmt.Errorf("unexpected token after 'math' expression [%s]: %q; expecting ',', '|' or ')'", mes[len(mes)-1], lex.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseMathEntry(lex *lexer) (*mathEntry, error) {
|
||||
me, err := parseMathExpr(lex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !lex.isKeyword("as") {
|
||||
return nil, fmt.Errorf("missing 'as' after [%s]", me)
|
||||
}
|
||||
lex.nextToken()
|
||||
|
||||
resultField, err := parseFieldName(lex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse result name: %w", err)
|
||||
}
|
||||
|
||||
if !lex.isKeyword("=") {
|
||||
return nil, fmt.Errorf("missing '=' after %q", resultField)
|
||||
e := &mathEntry{
|
||||
resultField: resultField,
|
||||
expr: me,
|
||||
}
|
||||
lex.nextToken()
|
||||
|
||||
be, err := parseMathBinaryExpr(lex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse expression after '%q=': %w", resultField, err)
|
||||
}
|
||||
|
||||
neededFields := newFieldsSet()
|
||||
be.updateNeededFields(neededFields)
|
||||
|
||||
me := &mathEntry{
|
||||
resultField: resultField,
|
||||
be: be,
|
||||
neededFields: neededFields.getAll(),
|
||||
}
|
||||
return me, nil
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type mathBinaryExpr struct {
|
||||
isConst bool
|
||||
constValue float64
|
||||
constValueStr string
|
||||
|
||||
fieldName string
|
||||
|
||||
left *mathBinaryExpr
|
||||
right *mathBinaryExpr
|
||||
op string
|
||||
|
||||
mathFunc func(a, b float64) float64
|
||||
}
|
||||
|
||||
func (be *mathBinaryExpr) String() string {
|
||||
if be.isConst {
|
||||
return be.constValueStr
|
||||
}
|
||||
if be.fieldName != "" {
|
||||
return quoteTokenIfNeeded(be.fieldName)
|
||||
}
|
||||
|
||||
leftStr := be.left.String()
|
||||
rightStr := be.right.String()
|
||||
|
||||
if binaryOpPriority(be.op) > binaryOpPriority(be.left.op) {
|
||||
leftStr = "(" + leftStr + ")"
|
||||
}
|
||||
if binaryOpPriority(be.op) > binaryOpPriority(be.right.op) {
|
||||
rightStr = "(" + rightStr + ")"
|
||||
}
|
||||
if be.op == "unary_minus" {
|
||||
// Unary minus
|
||||
return "-" + rightStr
|
||||
}
|
||||
|
||||
return leftStr + " " + be.op + " " + rightStr
|
||||
}
|
||||
|
||||
func (be *mathBinaryExpr) updateNeededFields(neededFields fieldsSet) {
|
||||
if be.isConst {
|
||||
return
|
||||
}
|
||||
if be.fieldName != "" {
|
||||
neededFields.add(be.fieldName)
|
||||
return
|
||||
}
|
||||
be.left.updateNeededFields(neededFields)
|
||||
be.right.updateNeededFields(neededFields)
|
||||
}
|
||||
|
||||
func parseMathBinaryExpr(lex *lexer) (*mathBinaryExpr, error) {
|
||||
func parseMathExpr(lex *lexer) (*mathExpr, error) {
|
||||
// parse left operand
|
||||
leftParens := lex.isKeyword("(")
|
||||
left, err := parseMathBinaryExprOperand(lex)
|
||||
left, err := parseMathExprOperand(lex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if lex.isKeyword("|", ")", ",", "") {
|
||||
if lex.isKeyword("as", "|", ")", ",", "") {
|
||||
// There is no right operand
|
||||
return left, nil
|
||||
}
|
||||
|
||||
again:
|
||||
// parse operator
|
||||
op := lex.token
|
||||
lex.nextToken()
|
||||
for {
|
||||
// parse operator
|
||||
op := lex.token
|
||||
lex.nextToken()
|
||||
|
||||
mathFunc, err := getMathFuncForOp(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := getMathFuncForBinaryOp(op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse operator after [%s]: %w", left, err)
|
||||
}
|
||||
|
||||
// parse right operand
|
||||
right, err := parseMathBinaryExprOperand(lex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse right operand after [%s %s]: %w", left, op, err)
|
||||
}
|
||||
// parse right operand
|
||||
right, err := parseMathExprOperand(lex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse operand after [%s %s]: %w", left, op, err)
|
||||
}
|
||||
|
||||
be := &mathBinaryExpr{
|
||||
left: left,
|
||||
right: right,
|
||||
op: op,
|
||||
mathFunc: mathFunc,
|
||||
}
|
||||
me := &mathExpr{
|
||||
args: []*mathExpr{left, right},
|
||||
op: op,
|
||||
f: f,
|
||||
}
|
||||
|
||||
// balance operands according to their priority
|
||||
if !leftParens && binaryOpPriority(op) > binaryOpPriority(left.op) {
|
||||
be.left = left.right
|
||||
left.right = be
|
||||
be = left
|
||||
}
|
||||
// balance operands according to their priority
|
||||
if !left.wrappedInParens && isMathBinaryOp(left.op) && getMathBinaryOpPriority(left.op) > getMathBinaryOpPriority(op) {
|
||||
me.args[0] = left.args[1]
|
||||
left.args[1] = me
|
||||
me = left
|
||||
}
|
||||
|
||||
if !lex.isKeyword("|", ")", ",", "") {
|
||||
left = be
|
||||
goto again
|
||||
}
|
||||
|
||||
return be, nil
|
||||
}
|
||||
|
||||
func getMathFuncForOp(op string) (func(a, b float64) float64, error) {
|
||||
switch op {
|
||||
case "+":
|
||||
return mathFuncPlus, nil
|
||||
case "-":
|
||||
return mathFuncMinus, nil
|
||||
case "*":
|
||||
return mathFuncMul, nil
|
||||
case "/":
|
||||
return mathFuncDiv, nil
|
||||
case "%":
|
||||
return mathFuncMod, nil
|
||||
case "^":
|
||||
return mathFuncPow, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported math operator: %q; supported operators: '+', '-', '*', '/'", op)
|
||||
if !lex.isKeyword("as", "|", ")", ",", "") {
|
||||
left = me
|
||||
continue
|
||||
}
|
||||
return me, nil
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncPlus(a, b float64) float64 {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func mathFuncMinus(a, b float64) float64 {
|
||||
return a - b
|
||||
}
|
||||
|
||||
func mathFuncMul(a, b float64) float64 {
|
||||
return a * b
|
||||
}
|
||||
|
||||
func mathFuncDiv(a, b float64) float64 {
|
||||
return a / b
|
||||
}
|
||||
|
||||
func mathFuncMod(a, b float64) float64 {
|
||||
return math.Mod(a, b)
|
||||
}
|
||||
|
||||
func mathFuncPow(a, b float64) float64 {
|
||||
return math.Pow(a, b)
|
||||
}
|
||||
|
||||
func binaryOpPriority(op string) int {
|
||||
switch op {
|
||||
case "+", "-":
|
||||
return 10
|
||||
case "*", "/", "%":
|
||||
return 20
|
||||
case "^":
|
||||
return 30
|
||||
case "unary_minus":
|
||||
return 40
|
||||
case "":
|
||||
return 100
|
||||
default:
|
||||
logger.Panicf("BUG: unexpected binary operation %q", op)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func parseMathBinaryExprInParens(lex *lexer) (*mathBinaryExpr, error) {
|
||||
func parseMathExprInParens(lex *lexer) (*mathExpr, error) {
|
||||
if !lex.isKeyword("(") {
|
||||
return nil, fmt.Errorf("missing '('")
|
||||
}
|
||||
lex.nextToken()
|
||||
|
||||
be, err := parseMathBinaryExpr(lex)
|
||||
me, err := parseMathExpr(lex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
me.wrappedInParens = true
|
||||
|
||||
if !lex.isKeyword(")") {
|
||||
return nil, fmt.Errorf("missing ')'; got %q instead", lex.token)
|
||||
}
|
||||
lex.nextToken()
|
||||
return be, nil
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func parseMathBinaryExprOperand(lex *lexer) (*mathBinaryExpr, error) {
|
||||
func parseMathExprOperand(lex *lexer) (*mathExpr, error) {
|
||||
if lex.isKeyword("(") {
|
||||
return parseMathBinaryExprInParens(lex)
|
||||
return parseMathExprInParens(lex)
|
||||
}
|
||||
|
||||
if lex.isKeyword("-") {
|
||||
switch {
|
||||
case lex.isKeyword("abs"):
|
||||
return parseMathExprAbs(lex)
|
||||
case lex.isKeyword("min"):
|
||||
return parseMathExprMin(lex)
|
||||
case lex.isKeyword("max"):
|
||||
return parseMathExprMax(lex)
|
||||
case lex.isKeyword("-"):
|
||||
return parseMathExprUnaryMinus(lex)
|
||||
case lex.isKeyword("+"):
|
||||
// just skip unary plus
|
||||
lex.nextToken()
|
||||
be, err := parseMathBinaryExprOperand(lex)
|
||||
return parseMathExprOperand(lex)
|
||||
case lex.isNumber():
|
||||
return parseMathExprConstNumber(lex)
|
||||
default:
|
||||
return parseMathExprFieldName(lex)
|
||||
}
|
||||
}
|
||||
|
||||
func parseMathExprAbs(lex *lexer) (*mathExpr, error) {
|
||||
me, err := parseMathExprGenericFunc(lex, "abs", mathFuncAbs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(me.args) != 1 {
|
||||
return nil, fmt.Errorf("'abs' function accepts only one arg; got %d args: [%s]", len(me.args), me)
|
||||
}
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func parseMathExprMin(lex *lexer) (*mathExpr, error) {
|
||||
me, err := parseMathExprGenericFunc(lex, "min", mathFuncMin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(me.args) < 2 {
|
||||
return nil, fmt.Errorf("'min' function needs at least 2 args; got %d args: [%s]", len(me.args), me)
|
||||
}
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func parseMathExprMax(lex *lexer) (*mathExpr, error) {
|
||||
me, err := parseMathExprGenericFunc(lex, "max", mathFuncMax)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(me.args) < 2 {
|
||||
return nil, fmt.Errorf("'max' function needs at least 2 args; got %d args: [%s]", len(me.args), me)
|
||||
}
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func parseMathExprGenericFunc(lex *lexer, funcName string, f mathFunc) (*mathExpr, error) {
|
||||
if !lex.isKeyword(funcName) {
|
||||
return nil, fmt.Errorf("missing %q keyword", funcName)
|
||||
}
|
||||
lex.nextToken()
|
||||
|
||||
args, err := parseMathFuncArgs(lex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse args for %q function: %w", funcName, err)
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return nil, fmt.Errorf("%q function needs at least one org", funcName)
|
||||
}
|
||||
me := &mathExpr{
|
||||
args: args,
|
||||
op: funcName,
|
||||
f: f,
|
||||
}
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func parseMathFuncArgs(lex *lexer) ([]*mathExpr, error) {
|
||||
if !lex.isKeyword("(") {
|
||||
return nil, fmt.Errorf("missing '('")
|
||||
}
|
||||
lex.nextToken()
|
||||
|
||||
var args []*mathExpr
|
||||
for {
|
||||
if lex.isKeyword(")") {
|
||||
lex.nextToken()
|
||||
return args, nil
|
||||
}
|
||||
|
||||
me, err := parseMathExpr(lex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
be = &mathBinaryExpr{
|
||||
left: &mathBinaryExpr{
|
||||
isConst: true,
|
||||
},
|
||||
right: be,
|
||||
op: "unary_minus",
|
||||
mathFunc: mathFuncMinus,
|
||||
}
|
||||
return be, nil
|
||||
}
|
||||
args = append(args, me)
|
||||
|
||||
if lex.isNumber() {
|
||||
numStr, err := getCompoundMathToken(lex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse number: %w", err)
|
||||
switch {
|
||||
case lex.isKeyword(")"):
|
||||
case lex.isKeyword(","):
|
||||
lex.nextToken()
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected token after [%s]: %q; want ',' or ')'", me, lex.token)
|
||||
}
|
||||
f, ok := tryParseNumber(numStr)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot parse number from %q", numStr)
|
||||
}
|
||||
be := &mathBinaryExpr{
|
||||
isConst: true,
|
||||
constValue: f,
|
||||
constValueStr: numStr,
|
||||
}
|
||||
return be, nil
|
||||
}
|
||||
}
|
||||
|
||||
fieldName, err := getCompoundMathToken(lex)
|
||||
func parseMathExprUnaryMinus(lex *lexer) (*mathExpr, error) {
|
||||
if !lex.isKeyword("-") {
|
||||
return nil, fmt.Errorf("missing '-'")
|
||||
}
|
||||
lex.nextToken()
|
||||
|
||||
expr, err := parseMathExprOperand(lex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
me := &mathExpr{
|
||||
args: []*mathExpr{expr},
|
||||
op: "unary_minus",
|
||||
f: mathFuncUnaryMinus,
|
||||
}
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func parseMathExprConstNumber(lex *lexer) (*mathExpr, error) {
|
||||
if !lex.isNumber() {
|
||||
return nil, fmt.Errorf("cannot parse number from %q", lex.token)
|
||||
}
|
||||
numStr, err := getCompoundMathToken(lex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse number: %w", err)
|
||||
}
|
||||
f, ok := tryParseNumber(numStr)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot parse number from %q", numStr)
|
||||
}
|
||||
me := &mathExpr{
|
||||
isConst: true,
|
||||
constValue: f,
|
||||
constValueStr: numStr,
|
||||
}
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func parseMathExprFieldName(lex *lexer) (*mathExpr, error) {
|
||||
fieldName, err := parseFieldName(lex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fieldName = getCanonicalColumnName(fieldName)
|
||||
be := &mathBinaryExpr{
|
||||
me := &mathExpr{
|
||||
fieldName: fieldName,
|
||||
}
|
||||
return be, nil
|
||||
return me, nil
|
||||
}
|
||||
|
||||
func getCompoundMathToken(lex *lexer) (string, error) {
|
||||
stopTokens := []string{"+", "*", "/", "%", "^", ",", ")", "|", ""}
|
||||
stopTokens := []string{"=", "+", "-", "*", "/", "%", "^", ",", ")", "|", ""}
|
||||
if lex.isKeyword(stopTokens...) {
|
||||
return "", fmt.Errorf("compound token cannot start with '%s'", lex.token)
|
||||
}
|
||||
|
@ -488,7 +639,6 @@ func getCompoundMathToken(lex *lexer) (string, error) {
|
|||
rawS := lex.rawToken
|
||||
lex.nextToken()
|
||||
suffix := ""
|
||||
stopTokens = append(stopTokens, "-")
|
||||
for !lex.isSkippedSpace && !lex.isKeyword(stopTokens...) {
|
||||
s += lex.token
|
||||
lex.nextToken()
|
||||
|
@ -498,3 +648,89 @@ func getCompoundMathToken(lex *lexer) (string, error) {
|
|||
}
|
||||
return rawS + suffix, nil
|
||||
}
|
||||
|
||||
func mathFuncPlus(result []float64, args [][]float64) {
|
||||
a := args[0]
|
||||
b := args[1]
|
||||
for i := range result {
|
||||
result[i] = a[i] + b[i]
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncMinus(result []float64, args [][]float64) {
|
||||
a := args[0]
|
||||
b := args[1]
|
||||
for i := range result {
|
||||
result[i] = a[i] - b[i]
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncMul(result []float64, args [][]float64) {
|
||||
a := args[0]
|
||||
b := args[1]
|
||||
for i := range result {
|
||||
result[i] = a[i] * b[i]
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncDiv(result []float64, args [][]float64) {
|
||||
a := args[0]
|
||||
b := args[1]
|
||||
for i := range result {
|
||||
result[i] = a[i] / b[i]
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncMod(result []float64, args [][]float64) {
|
||||
a := args[0]
|
||||
b := args[1]
|
||||
for i := range result {
|
||||
result[i] = math.Mod(a[i], b[i])
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncPow(result []float64, args [][]float64) {
|
||||
a := args[0]
|
||||
b := args[1]
|
||||
for i := range result {
|
||||
result[i] = math.Pow(a[i], b[i])
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncAbs(result []float64, args [][]float64) {
|
||||
arg := args[0]
|
||||
for i := range result {
|
||||
result[i] = math.Abs(arg[i])
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncUnaryMinus(result []float64, args [][]float64) {
|
||||
arg := args[0]
|
||||
for i := range result {
|
||||
result[i] = -arg[i]
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncMin(result []float64, args [][]float64) {
|
||||
for i := range result {
|
||||
f := nan
|
||||
for _, arg := range args {
|
||||
if math.IsNaN(f) || arg[i] < f {
|
||||
f = arg[i]
|
||||
}
|
||||
}
|
||||
result[i] = f
|
||||
}
|
||||
}
|
||||
|
||||
func mathFuncMax(result []float64, args [][]float64) {
|
||||
for i := range result {
|
||||
f := nan
|
||||
for _, arg := range args {
|
||||
if math.IsNaN(f) || arg[i] > f {
|
||||
f = arg[i]
|
||||
}
|
||||
}
|
||||
result[i] = f
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,15 +10,16 @@ func TestParsePipeMathSuccess(t *testing.T) {
|
|||
expectParsePipeSuccess(t, pipeStr)
|
||||
}
|
||||
|
||||
f(`math a = b`)
|
||||
f(`math a = -123`)
|
||||
f(`math a = 12.345KB`)
|
||||
f(`math a = -2 + 2`)
|
||||
f(`math a = x, y = z`)
|
||||
f(`math a = foo / bar + baz * abc % -45ms`)
|
||||
f(`math a = foo / (bar + baz) * abc ^ 2`)
|
||||
f(`math a = foo / ((bar + baz) * abc) ^ -2`)
|
||||
f(`math a = foo + bar / baz - abc`)
|
||||
f(`math b as a`)
|
||||
f(`math -123 as a`)
|
||||
f(`math 12.345KB as a`)
|
||||
f(`math (-2 + 2) as a`)
|
||||
f(`math min(3, foo, (1 + bar) / baz) as a, max(a, b) as b, (abs(c) + 5) as d`)
|
||||
f(`math x as a, z as y`)
|
||||
f(`math (foo / bar + baz * abc % -45ms) as a`)
|
||||
f(`math (foo / (bar + baz) * abc ^ 2) as a`)
|
||||
f(`math (foo / ((bar + baz) * abc) ^ -2) as a`)
|
||||
f(`math (foo + bar / baz - abc) as a`)
|
||||
}
|
||||
|
||||
func TestParsePipeMathFailure(t *testing.T) {
|
||||
|
@ -30,11 +31,13 @@ func TestParsePipeMathFailure(t *testing.T) {
|
|||
f(`math`)
|
||||
f(`math x`)
|
||||
f(`math x y`)
|
||||
f(`math x =`)
|
||||
f(`math x = (`)
|
||||
f(`math x = a +`)
|
||||
f(`math x = a + (`)
|
||||
f(`math x = a + )`)
|
||||
f(`math x as`)
|
||||
f(`math abs() as x`)
|
||||
f(`math abs(a, b) as x`)
|
||||
f(`math min() as x`)
|
||||
f(`math min(a) as x`)
|
||||
f(`math max() as x`)
|
||||
f(`math max(a) as x`)
|
||||
}
|
||||
|
||||
func TestPipeMath(t *testing.T) {
|
||||
|
@ -43,7 +46,7 @@ func TestPipeMath(t *testing.T) {
|
|||
expectPipeResults(t, pipeStr, rows, rowsExpected)
|
||||
}
|
||||
|
||||
f("math a = 1", [][]Field{
|
||||
f("math 1 as a", [][]Field{
|
||||
{
|
||||
{"a", "v1"},
|
||||
{"b", "2"},
|
||||
|
@ -57,7 +60,7 @@ func TestPipeMath(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
f("math a = 10 * 5 - 3", [][]Field{
|
||||
f("math 10 * 5 - 3 as a", [][]Field{
|
||||
{
|
||||
{"a", "v1"},
|
||||
{"b", "2"},
|
||||
|
@ -71,7 +74,7 @@ func TestPipeMath(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
f("math a = -1.5K", [][]Field{
|
||||
f("math -1.5K as a", [][]Field{
|
||||
{
|
||||
{"a", "v1"},
|
||||
{"b", "2"},
|
||||
|
@ -85,7 +88,7 @@ func TestPipeMath(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
f("math a = b", [][]Field{
|
||||
f("math b as a", [][]Field{
|
||||
{
|
||||
{"a", "v1"},
|
||||
{"b", "2"},
|
||||
|
@ -99,7 +102,7 @@ func TestPipeMath(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
f("math a = a", [][]Field{
|
||||
f("math a as a", [][]Field{
|
||||
{
|
||||
{"a", "v1"},
|
||||
{"b", "2"},
|
||||
|
@ -113,7 +116,7 @@ func TestPipeMath(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
f("math x = 2*c + b", [][]Field{
|
||||
f("math 2*c + b as x", [][]Field{
|
||||
{
|
||||
{"a", "v1"},
|
||||
{"b", "2"},
|
||||
|
@ -128,7 +131,7 @@ func TestPipeMath(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
f("math a = (2*c + (b%c))/(c-b)^(b-1)", [][]Field{
|
||||
f("math (2*c + (b%c))/(c-b)^(b-1) as a", [][]Field{
|
||||
{
|
||||
{"a", "v"},
|
||||
{"b", "2"},
|
||||
|
|
Loading…
Reference in a new issue