mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-02 09:10:40 +01:00
1299 lines
31 KiB
Go
1299 lines
31 KiB
Go
package promql
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Panicf controls how this package reports a runtime error indicative of a
|
|
// bug in the implementation.
|
|
var Panicf func(format string, args ...interface{}) = func(format string, args ...interface{}) {
|
|
panic(fmt.Errorf(format, args...))
|
|
}
|
|
|
|
// A Parser is a thread-safe object that can be used to parse Extended PromQL
|
|
// into a parsed tree of objects
|
|
type Parser struct {
|
|
compileRegexpAnchored func(re string) (*regexp.Regexp, error)
|
|
}
|
|
|
|
// NewParser constructs a new Parser
|
|
func NewParser(
|
|
compileRegexpAnchored func(re string) (*regexp.Regexp, error),
|
|
) *Parser {
|
|
return &Parser{
|
|
compileRegexpAnchored: compileRegexpAnchored,
|
|
}
|
|
}
|
|
|
|
// ParsePromQL parses an extended PromQL string into an Expr object
|
|
func (p *Parser) ParsePromQL(s string) (Expr, error) {
|
|
e, err := p.ParseRawPromQL(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
was := p.getDefaultWithArgExprs()
|
|
if e, err = p.expandWithExpr(was, e); err != nil {
|
|
return nil, fmt.Errorf(`cannot expand WITH expressions: %s`, err)
|
|
}
|
|
e = removeParensExpr(e)
|
|
return e, nil
|
|
}
|
|
|
|
// ParseRawPromQL parses an extended PromQL string into an Expr object, without
|
|
// rewriting or expanding with clauses
|
|
func (p *Parser) ParseRawPromQL(s string) (Expr, error) {
|
|
var ps parseState
|
|
ps.lex.Init(s)
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, fmt.Errorf(`cannot find the first token: %s`, err)
|
|
}
|
|
e, err := ps.parseExpr()
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`%s; unparsed data: %q`, err, ps.lex.Context())
|
|
}
|
|
if !isEOF(ps.lex.Token) {
|
|
return nil, fmt.Errorf(`unparsed data left: %q`, ps.lex.Context())
|
|
}
|
|
return e, nil
|
|
}
|
|
|
|
func (p *Parser) expandWithExpr(was []*WithArgExpr, e Expr) (Expr, error) {
|
|
switch t := e.(type) {
|
|
case *BinaryOpExpr:
|
|
left, err := p.expandWithExpr(was, t.Left)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
right, err := p.expandWithExpr(was, t.Right)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
groupModifierArgs, err := p.expandModifierArgs(was, t.GroupModifier.Args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
joinModifierArgs, err := p.expandModifierArgs(was, t.JoinModifier.Args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if t.Op == "+" {
|
|
lse, lok := left.(*StringExpr)
|
|
rse, rok := right.(*StringExpr)
|
|
if lok && rok {
|
|
se := &StringExpr{
|
|
S: lse.S + rse.S,
|
|
}
|
|
return se, nil
|
|
}
|
|
}
|
|
be := &BinaryOpExpr{
|
|
Op: t.Op,
|
|
Bool: t.Bool,
|
|
GroupModifier: t.GroupModifier,
|
|
JoinModifier: t.JoinModifier,
|
|
Left: left,
|
|
Right: right,
|
|
}
|
|
be.GroupModifier.Args = groupModifierArgs
|
|
be.JoinModifier.Args = joinModifierArgs
|
|
pe := ParensExpr{be}
|
|
return &pe, nil
|
|
case *FuncExpr:
|
|
args, err := p.expandWithArgs(was, t.Args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
wa := getWithArgExpr(was, t.Name)
|
|
if wa == nil {
|
|
fe := &FuncExpr{
|
|
Name: t.Name,
|
|
Args: args,
|
|
}
|
|
return fe, nil
|
|
}
|
|
return p.expandWithExprExt(was, wa, args)
|
|
case *AggrFuncExpr:
|
|
args, err := p.expandWithArgs(was, t.Args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
modifierArgs, err := p.expandModifierArgs(was, t.Modifier.Args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ae := &AggrFuncExpr{
|
|
Name: t.Name,
|
|
Args: args,
|
|
Modifier: t.Modifier,
|
|
}
|
|
ae.Modifier.Args = modifierArgs
|
|
return ae, nil
|
|
case *ParensExpr:
|
|
exprs, err := p.expandWithArgs(was, *t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pe := ParensExpr(exprs)
|
|
return &pe, nil
|
|
case *StringTemplateExpr:
|
|
var b []byte
|
|
for _, token := range t.Tokens {
|
|
if !token.Ident {
|
|
b = append(b, token.S...)
|
|
continue
|
|
}
|
|
wa := getWithArgExpr(was, token.S)
|
|
if wa == nil {
|
|
return nil, fmt.Errorf("missing %q value inside stringExpr", token.S)
|
|
}
|
|
eNew, err := p.expandWithExprExt(was, wa, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
seSrc, ok := eNew.(*StringExpr)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%q must be string expression; got %q", token.S, eNew.AppendString(nil))
|
|
}
|
|
b = append(b, seSrc.S...)
|
|
}
|
|
se := &StringExpr{
|
|
S: string(b),
|
|
}
|
|
return se, nil
|
|
case *RollupExpr:
|
|
eNew, err := p.expandWithExpr(was, t.Expr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
re := *t
|
|
re.Expr = eNew
|
|
return &re, nil
|
|
case *WithExpr:
|
|
wasNew := make([]*WithArgExpr, 0, len(was)+len(t.Was))
|
|
wasNew = append(wasNew, was...)
|
|
wasNew = append(wasNew, t.Was...)
|
|
eNew, err := p.expandWithExpr(wasNew, t.Expr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return eNew, nil
|
|
case *MetricTemplateExpr:
|
|
var newMe MetricExpr
|
|
// Populate converted tag filters
|
|
for _, tfe := range t.TagFilters {
|
|
if tfe.Value == nil {
|
|
// Expand tfe.Key into TagFilters.
|
|
wa := getWithArgExpr(was, tfe.Key)
|
|
if wa == nil {
|
|
return nil, fmt.Errorf("missing %q value inside %q", tfe.Key, t.AppendString(nil))
|
|
}
|
|
eNew, err := p.expandWithExprExt(was, wa, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
wme, ok := eNew.(*MetricExpr)
|
|
if !ok || wme.HasNonEmptyMetricGroup() {
|
|
return nil, fmt.Errorf("%q must be filters expression inside %q; got %q", tfe.Key, t.AppendString(nil), eNew.AppendString(nil))
|
|
}
|
|
newMe.TagFilters = append(newMe.TagFilters, wme.TagFilters...)
|
|
continue
|
|
}
|
|
|
|
// convert tfe to TagFilter.
|
|
se, err := p.expandWithExpr(was, tfe.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tf, err := p.createTagFilter(tfe.Key, se.(*StringExpr).S, tfe.IsRegexp, tfe.IsNegative)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newMe.TagFilters = append(newMe.TagFilters, *tf)
|
|
}
|
|
newMe.TagFilters = p.removeDuplicateTagFilters(newMe.TagFilters)
|
|
if !newMe.HasNonEmptyMetricGroup() {
|
|
return &newMe, nil
|
|
}
|
|
k := string(appendEscapedIdent(nil, newMe.TagFilters[0].Value))
|
|
wa := getWithArgExpr(was, k)
|
|
if wa == nil {
|
|
return &newMe, nil
|
|
}
|
|
eNew, err := p.expandWithExprExt(was, wa, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var wme *MetricExpr
|
|
re, _ := eNew.(*RollupExpr)
|
|
if re != nil {
|
|
wme, _ = re.Expr.(*MetricExpr)
|
|
} else {
|
|
wme, _ = eNew.(*MetricExpr)
|
|
}
|
|
if wme == nil {
|
|
if !newMe.IsOnlyMetricGroup() {
|
|
return nil, fmt.Errorf("cannot expand %q to non-metric expression %q", t.AppendString(nil), eNew.AppendString(nil))
|
|
}
|
|
return eNew, nil
|
|
}
|
|
|
|
rest := newMe.TagFilters[1:]
|
|
newMe.TagFilters = append(make([]TagFilter, 0, len(wme.TagFilters)+len(rest)), wme.TagFilters...)
|
|
newMe.TagFilters = append(newMe.TagFilters, rest...)
|
|
newMe.TagFilters = p.removeDuplicateTagFilters(newMe.TagFilters)
|
|
|
|
if re == nil {
|
|
return &newMe, nil
|
|
}
|
|
reNew := *re
|
|
reNew.Expr = &newMe
|
|
return &reNew, nil
|
|
default:
|
|
return e, nil
|
|
}
|
|
}
|
|
|
|
func (p *Parser) expandWithArgs(was []*WithArgExpr, args []Expr) ([]Expr, error) {
|
|
dstArgs := make([]Expr, len(args))
|
|
for i, arg := range args {
|
|
dstArg, err := p.expandWithExpr(was, arg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dstArgs[i] = dstArg
|
|
}
|
|
return dstArgs, nil
|
|
}
|
|
|
|
func (p *Parser) expandModifierArgs(was []*WithArgExpr, args []string) ([]string, error) {
|
|
if len(args) == 0 {
|
|
return nil, nil
|
|
}
|
|
dstArgs := make([]string, 0, len(args))
|
|
for _, arg := range args {
|
|
wa := getWithArgExpr(was, arg)
|
|
if wa == nil {
|
|
// Leave the arg as is.
|
|
dstArgs = append(dstArgs, arg)
|
|
continue
|
|
}
|
|
if len(wa.Args) > 0 {
|
|
// Template funcs cannot be used inside modifier list. Leave the arg as is.
|
|
dstArgs = append(dstArgs, arg)
|
|
continue
|
|
}
|
|
me, ok := wa.Expr.(*MetricExpr)
|
|
if ok {
|
|
if !me.IsOnlyMetricGroup() {
|
|
return nil, fmt.Errorf("cannot use %q instead of %q in %s", me.AppendString(nil), arg, args)
|
|
}
|
|
dstArg := string(me.TagFilters[0].Value)
|
|
dstArgs = append(dstArgs, dstArg)
|
|
continue
|
|
}
|
|
pe, ok := wa.Expr.(*ParensExpr)
|
|
if ok {
|
|
for _, pArg := range *pe {
|
|
me, ok := pArg.(*MetricExpr)
|
|
if !ok || !me.IsOnlyMetricGroup() {
|
|
return nil, fmt.Errorf("cannot use %q instead of %q in %s", pe.AppendString(nil), arg, args)
|
|
}
|
|
dstArg := string(me.TagFilters[0].Value)
|
|
dstArgs = append(dstArgs, dstArg)
|
|
}
|
|
continue
|
|
}
|
|
return nil, fmt.Errorf("cannot use %q instead of %q in %s", wa.Expr.AppendString(nil), arg, args)
|
|
}
|
|
|
|
// Remove duplicate args from dstArgs
|
|
m := make(map[string]bool, len(dstArgs))
|
|
filteredArgs := dstArgs[:0]
|
|
for _, arg := range dstArgs {
|
|
if !m[arg] {
|
|
filteredArgs = append(filteredArgs, arg)
|
|
m[arg] = true
|
|
}
|
|
}
|
|
return filteredArgs, nil
|
|
}
|
|
|
|
func (p *Parser) expandWithExprExt(was []*WithArgExpr, wa *WithArgExpr, args []Expr) (Expr, error) {
|
|
if len(wa.Args) != len(args) {
|
|
if args == nil {
|
|
// Just return metricExpr with the wa.Name name.
|
|
return newMetricExpr(wa.Name), nil
|
|
}
|
|
return nil, fmt.Errorf("invalid number of args for %q; got %d; want %d", wa.Name, len(args), len(wa.Args))
|
|
}
|
|
wasNew := make([]*WithArgExpr, 0, len(was)+len(args))
|
|
for _, waTmp := range was {
|
|
if waTmp == wa {
|
|
break
|
|
}
|
|
wasNew = append(wasNew, waTmp)
|
|
}
|
|
for i, arg := range args {
|
|
wasNew = append(wasNew, &WithArgExpr{
|
|
Name: wa.Args[i],
|
|
Expr: arg,
|
|
})
|
|
}
|
|
return p.expandWithExpr(wasNew, wa.Expr)
|
|
}
|
|
|
|
func (p *Parser) removeDuplicateTagFilters(tfs []TagFilter) []TagFilter {
|
|
tfsm := make(map[string]bool, len(tfs))
|
|
tfsNew := tfs[:0]
|
|
var bb []byte
|
|
for i := range tfs {
|
|
tf := &tfs[i]
|
|
bb = appendStringTagFilter(bb[:0], tf)
|
|
if tfsm[string(bb)] {
|
|
continue
|
|
}
|
|
tfsm[string(bb)] = true
|
|
tfsNew = append(tfsNew, *tf)
|
|
}
|
|
return tfsNew
|
|
}
|
|
|
|
func (p *Parser) createTagFilter(key, value string, isRegexp, isNegative bool) (*TagFilter, error) {
|
|
var tf TagFilter
|
|
tf.Key = []byte(unescapeIdent(key))
|
|
if len(key) == 0 {
|
|
tf.Value = []byte(unescapeIdent(value))
|
|
} else {
|
|
tf.Value = []byte(value)
|
|
}
|
|
if string(tf.Key) == "__name__" {
|
|
// This is required for storage.Search
|
|
tf.Key = nil
|
|
}
|
|
tf.IsRegexp = isRegexp
|
|
tf.IsNegative = isNegative
|
|
if !tf.IsRegexp {
|
|
return &tf, nil
|
|
}
|
|
|
|
// Verify regexp.
|
|
if _, err := p.compileRegexpAnchored(value); err != nil {
|
|
return nil, fmt.Errorf("invalid regexp in %s=%q: %s", tf.Key, tf.Value, err)
|
|
}
|
|
return &tf, nil
|
|
}
|
|
|
|
func (p *Parser) getDefaultWithArgExprs() []*WithArgExpr {
|
|
defaultWithArgExprsOnce.Do(func() {
|
|
defaultWithArgExprs = p.prepareWithArgExprs([]string{
|
|
// ru - resource utilization
|
|
`ru(freev, maxv) = clamp_min(maxv - clamp_min(freev, 0), 0) / clamp_min(maxv, 0) * 100`,
|
|
|
|
// ttf - time to fuckup
|
|
`ttf(freev) = smooth_exponential(
|
|
clamp_max(clamp_max(-freev, 0) / clamp_max(deriv_fast(freev), 0), 365*24*3600),
|
|
clamp_max(step()/300, 1)
|
|
)`,
|
|
|
|
`median_over_time(m) = quantile_over_time(0.5, m)`,
|
|
`range_median(q) = range_quantile(0.5, q)`,
|
|
`alias(q, name) = label_set(q, "__name__", name)`,
|
|
})
|
|
})
|
|
return defaultWithArgExprs
|
|
}
|
|
|
|
var (
|
|
defaultWithArgExprs []*WithArgExpr
|
|
defaultWithArgExprsOnce sync.Once
|
|
)
|
|
|
|
func (p *Parser) prepareWithArgExprs(ss []string) []*WithArgExpr {
|
|
was := make([]*WithArgExpr, len(ss))
|
|
for i, s := range ss {
|
|
was[i] = p.mustParseWithArgExpr(s)
|
|
}
|
|
if err := p.checkDuplicateWithArgNames(was); err != nil {
|
|
Panicf("BUG: %s", err)
|
|
}
|
|
return was
|
|
}
|
|
|
|
func (p *Parser) checkDuplicateWithArgNames(was []*WithArgExpr) error {
|
|
m := make(map[string]*WithArgExpr, len(was))
|
|
for _, wa := range was {
|
|
if waOld := m[wa.Name]; waOld != nil {
|
|
return fmt.Errorf("duplicate `with` arg name for: %s; previous one: %s", wa, waOld.AppendString(nil))
|
|
}
|
|
m[wa.Name] = wa
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) mustParseWithArgExpr(s string) *WithArgExpr {
|
|
var ps parseState
|
|
ps.lex.Init(s)
|
|
if err := ps.lex.Next(); err != nil {
|
|
Panicf("BUG: cannot find the first token in %q: %s", s, err)
|
|
}
|
|
wa, err := ps.parseWithArgExpr()
|
|
if err != nil {
|
|
Panicf("BUG: cannot parse %q: %s; unparsed data: %q", s, err, ps.lex.Context())
|
|
}
|
|
return wa
|
|
}
|
|
|
|
// removeParensExpr removes parensExpr for (expr) case.
|
|
func removeParensExpr(e Expr) Expr {
|
|
if re, ok := e.(*RollupExpr); ok {
|
|
re.Expr = removeParensExpr(re.Expr)
|
|
return re
|
|
}
|
|
if be, ok := e.(*BinaryOpExpr); ok {
|
|
be.Left = removeParensExpr(be.Left)
|
|
be.Right = removeParensExpr(be.Right)
|
|
return be
|
|
}
|
|
if ae, ok := e.(*AggrFuncExpr); ok {
|
|
for i, arg := range ae.Args {
|
|
ae.Args[i] = removeParensExpr(arg)
|
|
}
|
|
return ae
|
|
}
|
|
if fe, ok := e.(*FuncExpr); ok {
|
|
for i, arg := range fe.Args {
|
|
fe.Args[i] = removeParensExpr(arg)
|
|
}
|
|
return fe
|
|
}
|
|
if pe, ok := e.(*ParensExpr); ok {
|
|
args := *pe
|
|
for i, arg := range args {
|
|
args[i] = removeParensExpr(arg)
|
|
}
|
|
if len(*pe) == 1 {
|
|
return args[0]
|
|
}
|
|
// Treat parensExpr as a function with empty name, i.e. union()
|
|
fe := &FuncExpr{
|
|
Name: "",
|
|
Args: args,
|
|
}
|
|
return fe
|
|
}
|
|
return e
|
|
}
|
|
|
|
// parseState parses PromQL expression.
|
|
//
|
|
// preconditions for all parseState.parse* funcs:
|
|
// - p.lex.Token should point to the first token to parse.
|
|
//
|
|
// postconditions for all parseState.parse* funcs:
|
|
// - p.lex.Token should point to the next token after the parsed token.
|
|
type parseState struct {
|
|
parser *Parser
|
|
lex lexer
|
|
}
|
|
|
|
func isWith(s string) bool {
|
|
s = strings.ToLower(s)
|
|
return s == "with"
|
|
}
|
|
|
|
// parseWithExpr parses `WITH (withArgExpr...) expr`.
|
|
func (ps *parseState) parseWithExpr() (*WithExpr, error) {
|
|
var we WithExpr
|
|
if !isWith(ps.lex.Token) {
|
|
return nil, fmt.Errorf("withExpr: unexpected token %q; want `WITH`", ps.lex.Token)
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token != "(" {
|
|
return nil, fmt.Errorf(`withExpr: unexpected token %q; want "("`, ps.lex.Token)
|
|
}
|
|
for {
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token == ")" {
|
|
goto end
|
|
}
|
|
wa, err := ps.parseWithArgExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
we.Was = append(we.Was, wa)
|
|
switch ps.lex.Token {
|
|
case ",":
|
|
continue
|
|
case ")":
|
|
goto end
|
|
default:
|
|
return nil, fmt.Errorf(`withExpr: unexpected token %q; want ",", ")"`, ps.lex.Token)
|
|
}
|
|
}
|
|
|
|
end:
|
|
if err := ps.parser.checkDuplicateWithArgNames(we.Was); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
e, err := ps.parseExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
we.Expr = e
|
|
return &we, nil
|
|
}
|
|
|
|
func (ps *parseState) parseWithArgExpr() (*WithArgExpr, error) {
|
|
var wa WithArgExpr
|
|
if !isIdentPrefix(ps.lex.Token) {
|
|
return nil, fmt.Errorf(`withArgExpr: unexpected token %q; want "ident"`, ps.lex.Token)
|
|
}
|
|
wa.Name = ps.lex.Token
|
|
if isAggrFunc(wa.Name) || isRollupFunc(wa.Name) || isTransformFunc(wa.Name) || isWith(wa.Name) {
|
|
return nil, fmt.Errorf(`withArgExpr: cannot use reserved name %q`, wa.Name)
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token == "(" {
|
|
// Parse func args.
|
|
args, err := ps.parseIdentList()
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`withArgExpr: cannot parse args for %q: %s`, wa.Name, err)
|
|
}
|
|
// Make sure all the args have different names
|
|
m := make(map[string]bool, len(args))
|
|
for _, arg := range args {
|
|
if m[arg] {
|
|
return nil, fmt.Errorf(`withArgExpr: duplicate func arg found in %q: %q`, wa.Name, arg)
|
|
}
|
|
m[arg] = true
|
|
}
|
|
wa.Args = args
|
|
}
|
|
if ps.lex.Token != "=" {
|
|
return nil, fmt.Errorf(`withArgExpr: unexpected token %q; want "="`, ps.lex.Token)
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
e, err := ps.parseExpr()
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`withArgExpr: cannot parse %q: %s`, wa.Name, err)
|
|
}
|
|
wa.Expr = e
|
|
return &wa, nil
|
|
}
|
|
|
|
// parseExpr parses promql expr
|
|
func (ps *parseState) parseExpr() (Expr, error) {
|
|
e, err := ps.parseSingleExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
if !isBinaryOp(ps.lex.Token) {
|
|
return e, nil
|
|
}
|
|
|
|
var be BinaryOpExpr
|
|
be.Op = strings.ToLower(ps.lex.Token)
|
|
be.Left = e
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if isBinaryOpBoolModifier(ps.lex.Token) {
|
|
if !isBinaryOpCmp(be.Op) {
|
|
return nil, fmt.Errorf(`bool modifier cannot be applied to %q`, be.Op)
|
|
}
|
|
be.Bool = true
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if isBinaryOpGroupModifier(ps.lex.Token) {
|
|
if err := ps.parseModifierExpr(&be.GroupModifier); err != nil {
|
|
return nil, err
|
|
}
|
|
if isBinaryOpJoinModifier(ps.lex.Token) {
|
|
if isBinaryOpLogicalSet(be.Op) {
|
|
return nil, fmt.Errorf(`modifier %q cannot be applied to %q`, ps.lex.Token, be.Op)
|
|
}
|
|
if err := ps.parseModifierExpr(&be.JoinModifier); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
e2, err := ps.parseSingleExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
be.Right = e2
|
|
e = balanceBinaryOp(&be)
|
|
}
|
|
}
|
|
|
|
func balanceBinaryOp(be *BinaryOpExpr) Expr {
|
|
bel, ok := be.Left.(*BinaryOpExpr)
|
|
if !ok {
|
|
return be
|
|
}
|
|
lp := binaryOpPriority(bel.Op)
|
|
rp := binaryOpPriority(be.Op)
|
|
if rp < lp {
|
|
return be
|
|
}
|
|
if rp == lp && !isRightAssociativeBinaryOp(be.Op) {
|
|
return be
|
|
}
|
|
be.Left = bel.Right
|
|
bel.Right = balanceBinaryOp(be)
|
|
return bel
|
|
}
|
|
|
|
// parseSingleExpr parses non-binaryOp expressions.
|
|
func (ps *parseState) parseSingleExpr() (Expr, error) {
|
|
if isWith(ps.lex.Token) {
|
|
err := ps.lex.Next()
|
|
nextToken := ps.lex.Token
|
|
ps.lex.Prev()
|
|
if err == nil && nextToken == "(" {
|
|
return ps.parseWithExpr()
|
|
}
|
|
}
|
|
e, err := ps.parseSingleExprWithoutRollupSuffix()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token != "[" && !isOffset(ps.lex.Token) {
|
|
// There is no rollup expression.
|
|
return e, nil
|
|
}
|
|
return ps.parseRollupExpr(e)
|
|
}
|
|
|
|
func (ps *parseState) parseSingleExprWithoutRollupSuffix() (Expr, error) {
|
|
if isPositiveNumberPrefix(ps.lex.Token) || isInfOrNaN(ps.lex.Token) {
|
|
return ps.parsePositiveNumberExpr()
|
|
}
|
|
if isStringPrefix(ps.lex.Token) {
|
|
return ps.parseStringTemplateExpr()
|
|
}
|
|
if isIdentPrefix(ps.lex.Token) {
|
|
return ps.parseIdentExpr()
|
|
}
|
|
switch ps.lex.Token {
|
|
case "(":
|
|
return ps.parseParensExpr()
|
|
case "{":
|
|
return ps.parseMetricTemplateExpr()
|
|
case "-":
|
|
// Unary minus. Substitute -expr with (0 - expr)
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
e, err := ps.parseSingleExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Fall back in the simple -<number> case to a negative number
|
|
if ne, ok := e.(*NumberExpr); ok {
|
|
ne.N *= -1
|
|
return ne, nil
|
|
}
|
|
|
|
be := &BinaryOpExpr{
|
|
Op: "-",
|
|
Left: &NumberExpr{
|
|
N: 0,
|
|
},
|
|
Right: e,
|
|
}
|
|
pe := ParensExpr{be}
|
|
return &pe, nil
|
|
case "+":
|
|
// Unary plus
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
return ps.parseSingleExpr()
|
|
default:
|
|
return nil, fmt.Errorf(`singleExpr: unexpected token %q; want "(", "{", "-", "+"`, ps.lex.Token)
|
|
}
|
|
}
|
|
|
|
func (ps *parseState) parsePositiveNumberExpr() (*NumberExpr, error) {
|
|
if !isPositiveNumberPrefix(ps.lex.Token) && !isInfOrNaN(ps.lex.Token) {
|
|
return nil, fmt.Errorf(`positiveNumberExpr: unexpected token %q; want "number"`, ps.lex.Token)
|
|
}
|
|
|
|
n, err := strconv.ParseFloat(ps.lex.Token, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`positiveNumberExpr: cannot parse %q: %s`, ps.lex.Token, err)
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
ne := &NumberExpr{
|
|
N: n,
|
|
}
|
|
return ne, nil
|
|
}
|
|
|
|
func (ps *parseState) parseStringTemplateExpr() (*StringTemplateExpr, error) {
|
|
var se StringTemplateExpr
|
|
|
|
for {
|
|
switch {
|
|
case isStringPrefix(ps.lex.Token):
|
|
s, err := extractStringValue(ps.lex.Token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
se.Tokens = append(se.Tokens, StringToken{Ident: false, S: s})
|
|
case isIdentPrefix(ps.lex.Token):
|
|
se.Tokens = append(se.Tokens, StringToken{Ident: true, S: ps.lex.Token})
|
|
default:
|
|
return nil, fmt.Errorf(`stringExpr: unexpected token %q; want "string"`, ps.lex.Token)
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token != "+" {
|
|
return &se, nil
|
|
}
|
|
|
|
// composite stringExpr like `"s1" + "s2"`, `"s" + m()` or `"s" + m{}` or `"s" + unknownToken`.
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if isStringPrefix(ps.lex.Token) {
|
|
// "s1" + "s2"
|
|
continue
|
|
}
|
|
if !isIdentPrefix(ps.lex.Token) {
|
|
// "s" + unknownToken
|
|
ps.lex.Prev()
|
|
return &se, nil
|
|
}
|
|
// Look after ident
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token == "(" || ps.lex.Token == "{" {
|
|
// `"s" + m(` or `"s" + m{`
|
|
ps.lex.Prev()
|
|
ps.lex.Prev()
|
|
return &se, nil
|
|
}
|
|
// "s" + ident
|
|
ps.lex.Prev()
|
|
}
|
|
}
|
|
|
|
func (ps *parseState) parseParensExpr() (*ParensExpr, error) {
|
|
if ps.lex.Token != "(" {
|
|
return nil, fmt.Errorf(`parensExpr: unexpected token %q; want "("`, ps.lex.Token)
|
|
}
|
|
var exprs []Expr
|
|
for {
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token == ")" {
|
|
break
|
|
}
|
|
expr, err := ps.parseExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exprs = append(exprs, expr)
|
|
if ps.lex.Token == "," {
|
|
continue
|
|
}
|
|
if ps.lex.Token == ")" {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf(`parensExpr: unexpected token %q; want "," or ")"`, ps.lex.Token)
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
pe := ParensExpr(exprs)
|
|
return &pe, nil
|
|
}
|
|
|
|
func (ps *parseState) parseAggrFuncExpr() (*AggrFuncExpr, error) {
|
|
if !isAggrFunc(ps.lex.Token) {
|
|
return nil, fmt.Errorf(`aggrFuncExpr: unexpected token %q; want aggregate func`, ps.lex.Token)
|
|
}
|
|
|
|
var ae AggrFuncExpr
|
|
ae.Name = strings.ToLower(ps.lex.Token)
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if isIdentPrefix(ps.lex.Token) {
|
|
goto funcPrefixLabel
|
|
}
|
|
switch ps.lex.Token {
|
|
case "(":
|
|
goto funcArgsLabel
|
|
default:
|
|
return nil, fmt.Errorf(`aggrFuncExpr: unexpected token %q; want "("`, ps.lex.Token)
|
|
}
|
|
|
|
funcPrefixLabel:
|
|
{
|
|
if !isAggrFuncModifier(ps.lex.Token) {
|
|
return nil, fmt.Errorf(`aggrFuncExpr: unexpected token %q; want aggregate func modifier`, ps.lex.Token)
|
|
}
|
|
if err := ps.parseModifierExpr(&ae.Modifier); err != nil {
|
|
return nil, err
|
|
}
|
|
goto funcArgsLabel
|
|
}
|
|
|
|
funcArgsLabel:
|
|
{
|
|
args, err := ps.parseArgListExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ae.Args = args
|
|
|
|
// Verify whether func suffix exists.
|
|
if ae.Modifier.Op != "" || !isAggrFuncModifier(ps.lex.Token) {
|
|
return &ae, nil
|
|
}
|
|
if err := ps.parseModifierExpr(&ae.Modifier); err != nil {
|
|
return nil, err
|
|
}
|
|
return &ae, nil
|
|
}
|
|
}
|
|
|
|
func newMetricExpr(name string) *MetricExpr {
|
|
return &MetricExpr{
|
|
TagFilters: []TagFilter{{
|
|
Value: []byte(name),
|
|
}},
|
|
}
|
|
}
|
|
|
|
func extractStringValue(token string) (string, error) {
|
|
if !isStringPrefix(token) {
|
|
return "", fmt.Errorf(`stringExpr must contain only string literals; got %q`, token)
|
|
}
|
|
|
|
// See https://prometheus.io/docs/prometheus/latest/querying/basics/#string-literals
|
|
if token[0] == '\'' {
|
|
if len(token) < 2 || token[len(token)-1] != '\'' {
|
|
return "", fmt.Errorf(`string literal contains unexpected trailing char; got %q`, token)
|
|
}
|
|
token = token[1 : len(token)-1]
|
|
token = strings.Replace(token, "\\'", "'", -1)
|
|
token = strings.Replace(token, `"`, `\"`, -1)
|
|
token = `"` + token + `"`
|
|
}
|
|
s, err := strconv.Unquote(token)
|
|
if err != nil {
|
|
return "", fmt.Errorf(`cannot parse string literal %q: %s`, token, err)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (ps *parseState) parseFuncExpr() (*FuncExpr, error) {
|
|
if !isIdentPrefix(ps.lex.Token) {
|
|
return nil, fmt.Errorf(`funcExpr: unexpected token %q; want "ident"`, ps.lex.Token)
|
|
}
|
|
|
|
var fe FuncExpr
|
|
fe.Name = ps.lex.Token
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token != "(" {
|
|
return nil, fmt.Errorf(`funcExpr; unexpected token %q; want "("`, ps.lex.Token)
|
|
}
|
|
args, err := ps.parseArgListExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fe.Args = args
|
|
return &fe, nil
|
|
}
|
|
|
|
func (ps *parseState) parseModifierExpr(me *ModifierExpr) error {
|
|
if !isIdentPrefix(ps.lex.Token) {
|
|
return fmt.Errorf(`modifierExpr: unexpected token %q; want "ident"`, ps.lex.Token)
|
|
}
|
|
|
|
me.Op = strings.ToLower(ps.lex.Token)
|
|
|
|
if err := ps.lex.Next(); err != nil {
|
|
return err
|
|
}
|
|
if isBinaryOpJoinModifier(me.Op) && ps.lex.Token != "(" {
|
|
// join modifier may miss ident list.
|
|
return nil
|
|
}
|
|
args, err := ps.parseIdentList()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
me.Args = args
|
|
return nil
|
|
}
|
|
|
|
func (ps *parseState) parseIdentList() ([]string, error) {
|
|
if ps.lex.Token != "(" {
|
|
return nil, fmt.Errorf(`identList: unexpected token %q; want "("`, ps.lex.Token)
|
|
}
|
|
var idents []string
|
|
for {
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token == ")" {
|
|
goto closeParensLabel
|
|
}
|
|
if !isIdentPrefix(ps.lex.Token) {
|
|
return nil, fmt.Errorf(`identList: unexpected token %q; want "ident"`, ps.lex.Token)
|
|
}
|
|
idents = append(idents, ps.lex.Token)
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
switch ps.lex.Token {
|
|
case ",":
|
|
continue
|
|
case ")":
|
|
goto closeParensLabel
|
|
default:
|
|
return nil, fmt.Errorf(`identList: unexpected token %q; want ",", ")"`, ps.lex.Token)
|
|
}
|
|
}
|
|
|
|
closeParensLabel:
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
return idents, nil
|
|
}
|
|
|
|
func (ps *parseState) parseArgListExpr() ([]Expr, error) {
|
|
if ps.lex.Token != "(" {
|
|
return nil, fmt.Errorf(`argList: unexpected token %q; want "("`, ps.lex.Token)
|
|
}
|
|
var args []Expr
|
|
for {
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token == ")" {
|
|
goto closeParensLabel
|
|
}
|
|
expr, err := ps.parseExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args = append(args, expr)
|
|
switch ps.lex.Token {
|
|
case ",":
|
|
continue
|
|
case ")":
|
|
goto closeParensLabel
|
|
default:
|
|
return nil, fmt.Errorf(`argList: unexpected token %q; want ",", ")"`, ps.lex.Token)
|
|
}
|
|
}
|
|
|
|
closeParensLabel:
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
return args, nil
|
|
}
|
|
|
|
func getWithArgExpr(was []*WithArgExpr, name string) *WithArgExpr {
|
|
// Scan wes backwards, since certain expressions may override
|
|
// previously defined expressions
|
|
for i := len(was) - 1; i >= 0; i-- {
|
|
wa := was[i]
|
|
if wa.Name == name {
|
|
return wa
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ps *parseState) parseTagFilters() ([]*TagFilterExpr, error) {
|
|
if ps.lex.Token != "{" {
|
|
return nil, fmt.Errorf(`tagFilters: unexpected token %q; want "{"`, ps.lex.Token)
|
|
}
|
|
|
|
var tfes []*TagFilterExpr
|
|
for {
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token == "}" {
|
|
goto closeBracesLabel
|
|
}
|
|
tfe, err := ps.parseTagFilterExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tfes = append(tfes, tfe)
|
|
switch ps.lex.Token {
|
|
case ",":
|
|
continue
|
|
case "}":
|
|
goto closeBracesLabel
|
|
default:
|
|
return nil, fmt.Errorf(`tagFilters: unexpected token %q; want ",", "}"`, ps.lex.Token)
|
|
}
|
|
}
|
|
|
|
closeBracesLabel:
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
return tfes, nil
|
|
}
|
|
|
|
func (ps *parseState) parseTagFilterExpr() (*TagFilterExpr, error) {
|
|
if !isIdentPrefix(ps.lex.Token) {
|
|
return nil, fmt.Errorf(`tagFilterExpr: unexpected token %q; want "ident"`, ps.lex.Token)
|
|
}
|
|
var tfe TagFilterExpr
|
|
tfe.Key = ps.lex.Token
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch ps.lex.Token {
|
|
case "=":
|
|
// Nothing to do.
|
|
case "!=":
|
|
tfe.IsNegative = true
|
|
case "=~":
|
|
tfe.IsRegexp = true
|
|
case "!~":
|
|
tfe.IsNegative = true
|
|
tfe.IsRegexp = true
|
|
case ",", "}":
|
|
return &tfe, nil
|
|
default:
|
|
return nil, fmt.Errorf(`tagFilterExpr: unexpected token %q; want "=", "!=", "=~", "!~", ",", "}"`, ps.lex.Token)
|
|
}
|
|
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
se, err := ps.parseStringTemplateExpr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tfe.Value = se
|
|
return &tfe, nil
|
|
}
|
|
|
|
func (ps *parseState) parseWindowAndStep() (string, string, bool, error) {
|
|
if ps.lex.Token != "[" {
|
|
return "", "", false, fmt.Errorf(`windowAndStep: unexpected token %q; want "["`, ps.lex.Token)
|
|
}
|
|
err := ps.lex.Next()
|
|
if err != nil {
|
|
return "", "", false, err
|
|
}
|
|
var window string
|
|
if !strings.HasPrefix(ps.lex.Token, ":") {
|
|
window, err = ps.parseDuration()
|
|
if err != nil {
|
|
return "", "", false, err
|
|
}
|
|
}
|
|
var step string
|
|
inheritStep := false
|
|
if strings.HasPrefix(ps.lex.Token, ":") {
|
|
// Parse step
|
|
ps.lex.Token = ps.lex.Token[1:]
|
|
if ps.lex.Token == "" {
|
|
if err := ps.lex.Next(); err != nil {
|
|
return "", "", false, err
|
|
}
|
|
if ps.lex.Token == "]" {
|
|
inheritStep = true
|
|
}
|
|
}
|
|
if ps.lex.Token != "]" {
|
|
step, err = ps.parseDuration()
|
|
if err != nil {
|
|
return "", "", false, err
|
|
}
|
|
}
|
|
}
|
|
if ps.lex.Token != "]" {
|
|
return "", "", false, fmt.Errorf(`windowAndStep: unexpected token %q; want "]"`, ps.lex.Token)
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return "", "", false, err
|
|
}
|
|
return window, step, inheritStep, nil
|
|
}
|
|
|
|
func (ps *parseState) parseOffset() (string, error) {
|
|
if !isOffset(ps.lex.Token) {
|
|
return "", fmt.Errorf(`offset: unexpected token %q; want "offset"`, ps.lex.Token)
|
|
}
|
|
if err := ps.lex.Next(); err != nil {
|
|
return "", err
|
|
}
|
|
d, err := ps.parseDuration()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func (ps *parseState) parseDuration() (string, error) {
|
|
if !isDuration(ps.lex.Token) {
|
|
return "", fmt.Errorf(`duration: unexpected token %q; want "duration"`, ps.lex.Token)
|
|
}
|
|
d := ps.lex.Token
|
|
if err := ps.lex.Next(); err != nil {
|
|
return "", err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// parseIdentExpr parses expressions starting with `ident` token.
|
|
func (ps *parseState) parseIdentExpr() (Expr, error) {
|
|
// Look into the next-next token in order to determine how to parse
|
|
// the current expression.
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if isEOF(ps.lex.Token) || isOffset(ps.lex.Token) {
|
|
ps.lex.Prev()
|
|
return ps.parseMetricTemplateExpr()
|
|
}
|
|
if isIdentPrefix(ps.lex.Token) {
|
|
ps.lex.Prev()
|
|
if isAggrFunc(ps.lex.Token) {
|
|
return ps.parseAggrFuncExpr()
|
|
}
|
|
return ps.parseMetricTemplateExpr()
|
|
}
|
|
if isBinaryOp(ps.lex.Token) {
|
|
ps.lex.Prev()
|
|
return ps.parseMetricTemplateExpr()
|
|
}
|
|
switch ps.lex.Token {
|
|
case "(":
|
|
ps.lex.Prev()
|
|
if isAggrFunc(ps.lex.Token) {
|
|
return ps.parseAggrFuncExpr()
|
|
}
|
|
return ps.parseFuncExpr()
|
|
case "{", "[", ")", ",":
|
|
ps.lex.Prev()
|
|
return ps.parseMetricTemplateExpr()
|
|
default:
|
|
return nil, fmt.Errorf(`identExpr: unexpected token %q; want "(", "{", "[", ")", ","`, ps.lex.Token)
|
|
}
|
|
}
|
|
|
|
func (ps *parseState) parseMetricTemplateExpr() (*MetricTemplateExpr, error) {
|
|
var me MetricTemplateExpr
|
|
if isIdentPrefix(ps.lex.Token) {
|
|
var tfe TagFilterExpr
|
|
tfe.Value = &StringTemplateExpr{
|
|
Tokens: []StringToken{
|
|
{
|
|
Ident: false,
|
|
S: ps.lex.Token,
|
|
},
|
|
},
|
|
}
|
|
me.TagFilters = append(me.TagFilters[:0], &tfe)
|
|
if err := ps.lex.Next(); err != nil {
|
|
return nil, err
|
|
}
|
|
if ps.lex.Token != "{" {
|
|
return &me, nil
|
|
}
|
|
}
|
|
tfes, err := ps.parseTagFilters()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
me.TagFilters = append(me.TagFilters, tfes...)
|
|
return &me, nil
|
|
}
|
|
|
|
func (ps *parseState) parseRollupExpr(arg Expr) (Expr, error) {
|
|
var re RollupExpr
|
|
re.Expr = arg
|
|
if ps.lex.Token == "[" {
|
|
window, step, inheritStep, err := ps.parseWindowAndStep()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
re.Window = window
|
|
re.Step = step
|
|
re.InheritStep = inheritStep
|
|
if !isOffset(ps.lex.Token) {
|
|
return &re, nil
|
|
}
|
|
}
|
|
offset, err := ps.parseOffset()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
re.Offset = offset
|
|
return &re, nil
|
|
}
|
|
|
|
func appendStringTagFilter(dst []byte, tf *TagFilter) []byte {
|
|
if len(tf.Key) == 0 {
|
|
dst = append(dst, "__name__"...)
|
|
} else {
|
|
dst = appendEscapedIdent(dst, []byte(tf.Key))
|
|
}
|
|
dst = appendStringTagFilterOp(dst, tf.IsRegexp, tf.IsNegative)
|
|
return strconv.AppendQuote(dst, string(tf.Value))
|
|
}
|
|
|
|
func appendStringTagFilterOp(dst []byte, isRegexp, isNegative bool) []byte {
|
|
var op string
|
|
if isNegative {
|
|
if isRegexp {
|
|
op = "!~"
|
|
} else {
|
|
op = "!="
|
|
}
|
|
} else {
|
|
if isRegexp {
|
|
op = "=~"
|
|
} else {
|
|
op = "="
|
|
}
|
|
}
|
|
return append(dst, op...)
|
|
}
|