Skip to content

Commit 4bbc75c

Browse files
parse variables in expressions
1 parent 6ea382c commit 4bbc75c

File tree

3 files changed

+70
-13
lines changed

3 files changed

+70
-13
lines changed

lexer.go

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,12 @@ again:
122122
}
123123
goto tokenFoundLabel
124124
}
125-
if strings.HasPrefix(s, "$__interval") {
126-
lex.sTail = s[len("$__interval"):]
127-
return "$__interval", nil
128-
}
129-
if strings.HasPrefix(s, "$__rate_interval") {
130-
lex.sTail = s[len("$__rate_interval"):]
131-
return "$__interval", nil
125+
if isVariable(s) {
126+
token, err = scanVariable(s)
127+
if err != nil {
128+
return "", err
129+
}
130+
goto tokenFoundLabel
132131
}
133132
return "", fmt.Errorf("cannot recognize %q", s)
134133

@@ -301,6 +300,37 @@ func scanPositiveNumber(s string) (string, error) {
301300
return s[:j], nil
302301
}
303302

303+
func isVariable(s string) bool {
304+
return len(s) > 1 && s[0] == '$'
305+
}
306+
307+
func isVariableChar(c byte) bool {
308+
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'
309+
}
310+
311+
func scanVariable(s string) (string, error) {
312+
if len(s) < 2 {
313+
return "", fmt.Errorf("too small string for a variable %q", s)
314+
}
315+
hasBraces := s[1] == '{'
316+
i := 2
317+
for i < len(s) {
318+
if hasBraces {
319+
if s[i] == '}' {
320+
i++
321+
break
322+
}
323+
if !isVariableChar(s[i]) {
324+
return "", fmt.Errorf("not allowed symbol in variable %q", s)
325+
}
326+
} else if !isVariableChar(s[i]) {
327+
break
328+
}
329+
i++
330+
}
331+
return s[:i], nil
332+
}
333+
304334
func scanNumMultiplier(s string) int {
305335
if len(s) > 3 {
306336
s = s[:3]
@@ -534,7 +564,7 @@ func scanSpecialIntegerPrefix(s string) (skipChars int, isHex bool) {
534564
}
535565

536566
func isPositiveDuration(s string) bool {
537-
if s == "$__interval" {
567+
if s == "$__interval" || s == "$__rate_interval" {
538568
return true
539569
}
540570
n := scanDuration(s)
@@ -610,7 +640,7 @@ func DurationValue(s string, step int64) (int64, error) {
610640
}
611641

612642
func parseSingleDuration(s string, step int64) (float64, error) {
613-
if s == "$__interval" {
643+
if s == "$__interval" || s == "$__rate_interval" {
614644
return float64(step), nil
615645
}
616646

@@ -676,8 +706,8 @@ func scanSingleDuration(s string, canBeNegative bool) int {
676706
if s[0] == '-' && canBeNegative {
677707
i++
678708
}
679-
if s[i:] == "$__interval" {
680-
return i + len("$__interval")
709+
if s[i:] == "$__interval" || s[i:] == "$__rate_interval" {
710+
return i + len(s[i:])
681711
}
682712
for i < len(s) && isDecimalChar(s[i]) {
683713
i++

lexer_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,33 @@ func TestScanPositiveNumberFailure(t *testing.T) {
9898
f("12.34e-")
9999
}
100100

101+
func TestScanVariableSuccess(t *testing.T) {
102+
f := func(s, vsExpected string) {
103+
t.Helper()
104+
vs, err := scanVariable(s)
105+
if err != nil {
106+
t.Fatalf("unexpected error in scanVariable(%q): %s", s, err)
107+
}
108+
if vs != vsExpected {
109+
t.Fatalf("unexpected variable scanned from %q; got %q; want %q", s, vs, vsExpected)
110+
}
111+
}
112+
f("$__rate_interval", "$__rate_interval")
113+
f("${foobar},test", "${foobar}")
114+
}
115+
116+
func TestScanVariableFailure(t *testing.T) {
117+
f := func(s string) {
118+
t.Helper()
119+
vs, err := scanVariable(s)
120+
if err == nil {
121+
t.Fatalf("expecting non-nil error in scanVariable(%q); got result %q", s, vs)
122+
}
123+
}
124+
f("")
125+
f("${foobar,")
126+
}
127+
101128
func TestParsePositiveNumberSuccess(t *testing.T) {
102129
f := func(s string, vExpected float64) {
103130
t.Helper()

parser.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ func (p *parser) parseWindowAndStep() (*DurationExpr, *DurationExpr, bool, error
15371537
}
15381538
var window *DurationExpr
15391539
if !strings.HasPrefix(p.lex.Token, ":") {
1540-
if p.lex.Token == "$__interval" {
1540+
if p.lex.Token == "$__interval" || p.lex.Token == "$__rate_interval" {
15411541
// Skip $__interval, since it must be treated as missing lookbehind window,
15421542
// e.g. rate(m[$__interval]) must be equivalent to rate(m).
15431543
// In this case VictoriaMetrics automatically adjusts the lookbehind window
@@ -1656,7 +1656,7 @@ func (p *parser) parsePositiveDuration() (*DurationExpr, error) {
16561656
}
16571657
}
16581658
// Verify duration value.
1659-
if s == "$__interval" {
1659+
if s == "$__interval" || s == "$__rate_interval" {
16601660
s = "1i"
16611661
}
16621662
return newDurationExpr(s)

0 commit comments

Comments
 (0)