From 72d7b325a1e4eca096f12fc1a0ca861048cfc071 Mon Sep 17 00:00:00 2001
From: Julius Volz <julius.volz@gmail.com>
Date: Wed, 18 Feb 2015 02:30:41 +0100
Subject: [PATCH] Implement offset operator.

This allows changing the time offset for individual instant and range
vectors in a query.

For example, this returns the value of `foo` 5 minutes in the past
relative to the current query evaluation time:

    foo offset 5m

Note that the `offset` modifier always needs to follow the selector
immediately. I.e. the following would be correct:

    sum(foo offset 5m) // GOOD.

While the following would be *incorrect*:

    sum(foo) offset 5m // INVALID.

The same works for range vectors. This returns the 5-minutes-rate that
`foo` had a week ago:

    rate(foo[5m] offset 1w)

This change touches the following components:

* Lexer/parser: additions to correctly parse the new `offset`/`OFFSET`
  keyword.
* AST: vector and matrix nodes now have an additional `offset` field.
  This is used during their evaluation to adjust query and result times
  appropriately.
* Query analyzer: now works on separate sets of ranges and instants per
  offset. Isolating different offsets from each other completely in this
  way keeps the preloading code relatively simple.

No storage engine changes were needed by this change.

The rules tests have been changed to not probe the internal
implementation details of the query analyzer anymore (how many instants
and ranges have been preloaded). This would also become too cumbersome
to test with the new model, and measuring the result of the query should
be sufficient.

This fixes https://github.com/prometheus/prometheus/issues/529
This fixed https://github.com/prometheus/promdash/issues/201
---
 rules/ast/ast.go            |   22 +-
 rules/ast/query_analyzer.go |  186 +++---
 rules/helpers.go            |   30 +-
 rules/lexer.l               |    1 +
 rules/lexer.l.go            | 1081 +++++++++++++++++++----------------
 rules/parser.y              |   27 +-
 rules/parser.y.go           |  285 ++++-----
 rules/rules_test.go         |  257 ++-------
 8 files changed, 963 insertions(+), 926 deletions(-)

diff --git a/rules/ast/ast.go b/rules/ast/ast.go
index 8797c8fdec..a6f9e0195a 100644
--- a/rules/ast/ast.go
+++ b/rules/ast/ast.go
@@ -215,6 +215,7 @@ type (
 	// A VectorSelector represents a metric name plus labelset.
 	VectorSelector struct {
 		labelMatchers metric.LabelMatchers
+		offset        time.Duration
 		// The series iterators are populated at query analysis time.
 		iterators map[clientmodel.Fingerprint]local.SeriesIterator
 		metrics   map[clientmodel.Fingerprint]clientmodel.COWMetric
@@ -261,6 +262,7 @@ type (
 		// Fingerprints are populated from label matchers at query analysis time.
 		fingerprints clientmodel.Fingerprints
 		interval     time.Duration
+		offset       time.Duration
 	}
 )
 
@@ -561,8 +563,8 @@ func (node *VectorSelector) Eval(timestamp clientmodel.Timestamp) Vector {
 	//// timer := v.stats.GetTimer(stats.GetValueAtTimeTime).Start()
 	samples := Vector{}
 	for fp, it := range node.iterators {
-		sampleCandidates := it.GetValueAtTime(timestamp)
-		samplePair := chooseClosestSample(sampleCandidates, timestamp)
+		sampleCandidates := it.GetValueAtTime(timestamp.Add(-node.offset))
+		samplePair := chooseClosestSample(sampleCandidates, timestamp.Add(-node.offset))
 		if samplePair != nil {
 			samples = append(samples, &Sample{
 				Metric:    node.metrics[fp],
@@ -823,8 +825,8 @@ func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector {
 // the selector.
 func (node *MatrixSelector) Eval(timestamp clientmodel.Timestamp) Matrix {
 	interval := &metric.Interval{
-		OldestInclusive: timestamp.Add(-node.interval),
-		NewestInclusive: timestamp,
+		OldestInclusive: timestamp.Add(-node.interval - node.offset),
+		NewestInclusive: timestamp.Add(-node.offset),
 	}
 
 	//// timer := v.stats.GetTimer(stats.GetRangeValuesTime).Start()
@@ -835,6 +837,12 @@ func (node *MatrixSelector) Eval(timestamp clientmodel.Timestamp) Matrix {
 			continue
 		}
 
+		if node.offset != 0 {
+			for _, sp := range samplePairs {
+				sp.Timestamp = sp.Timestamp.Add(node.offset)
+			}
+		}
+
 		sampleStream := SampleStream{
 			Metric: node.metrics[fp],
 			Values: samplePairs,
@@ -910,9 +918,10 @@ func NewScalarLiteral(value clientmodel.SampleValue) *ScalarLiteral {
 
 // NewVectorSelector returns a (not yet evaluated) VectorSelector with
 // the given LabelSet.
-func NewVectorSelector(m metric.LabelMatchers) *VectorSelector {
+func NewVectorSelector(m metric.LabelMatchers, offset time.Duration) *VectorSelector {
 	return &VectorSelector{
 		labelMatchers: m,
+		offset:        offset,
 		iterators:     map[clientmodel.Fingerprint]local.SeriesIterator{},
 		metrics:       map[clientmodel.Fingerprint]clientmodel.COWMetric{},
 	}
@@ -1002,10 +1011,11 @@ func NewArithExpr(opType BinOpType, lhs Node, rhs Node) (Node, error) {
 
 // NewMatrixSelector returns a (not yet evaluated) MatrixSelector with
 // the given VectorSelector and Duration.
-func NewMatrixSelector(vector *VectorSelector, interval time.Duration) *MatrixSelector {
+func NewMatrixSelector(vector *VectorSelector, interval time.Duration, offset time.Duration) *MatrixSelector {
 	return &MatrixSelector{
 		labelMatchers: vector.labelMatchers,
 		interval:      interval,
+		offset:        offset,
 		iterators:     map[clientmodel.Fingerprint]local.SeriesIterator{},
 		metrics:       map[clientmodel.Fingerprint]clientmodel.COWMetric{},
 	}
diff --git a/rules/ast/query_analyzer.go b/rules/ast/query_analyzer.go
index f606773d44..b7087a36ab 100644
--- a/rules/ast/query_analyzer.go
+++ b/rules/ast/query_analyzer.go
@@ -22,28 +22,27 @@ import (
 	"github.com/prometheus/prometheus/storage/local"
 )
 
-// FullRangeMap maps the fingerprint of a full range to the duration
-// of the matrix selector it resulted from.
-type FullRangeMap map[clientmodel.Fingerprint]time.Duration
-
-// IntervalRangeMap is a set of fingerprints of interval ranges.
-type IntervalRangeMap map[clientmodel.Fingerprint]bool
+// preloadTimes tracks which instants or ranges to preload for a set of
+// fingerprints. One of these structs is collected for each offset by the query
+// analyzer.
+type preloadTimes struct {
+	// Instants require single samples to be loaded along the entire query
+	// range, with intervals between the samples corresponding to the query
+	// resolution.
+	instants map[clientmodel.Fingerprint]struct{}
+	// Ranges require loading a range of samples at each resolution step,
+	// stretching backwards from the current evaluation timestamp. The length of
+	// the range into the past is given by the duration, as in "foo[5m]".
+	ranges map[clientmodel.Fingerprint]time.Duration
+}
 
 // A QueryAnalyzer recursively traverses the AST to look for any nodes
 // which will need data from the datastore. Instantiate with
 // NewQueryAnalyzer.
 type QueryAnalyzer struct {
-	// Values collected by query analysis.
-	//
-	// Full ranges always implicitly span a time range of:
-	// - start: query interval start - duration
-	// - end:   query interval end
-	//
-	// This is because full ranges can only result from matrix selectors (like
-	// "foo[5m]"), which have said time-spanning behavior during a ranged query.
-	FullRanges FullRangeMap
-	// Interval ranges always implicitly span the whole query range.
-	IntervalRanges IntervalRangeMap
+	// Tracks one set of times to preload per offset that occurs in the query
+	// expression.
+	offsetPreloadTimes map[time.Duration]preloadTimes
 	// The underlying storage to which the query will be applied. Needed for
 	// extracting timeseries fingerprint information during query analysis.
 	storage local.Storage
@@ -54,37 +53,49 @@ type QueryAnalyzer struct {
 // fingerprint information during query analysis.
 func NewQueryAnalyzer(storage local.Storage) *QueryAnalyzer {
 	return &QueryAnalyzer{
-		FullRanges:     FullRangeMap{},
-		IntervalRanges: IntervalRangeMap{},
-		storage:        storage,
+		offsetPreloadTimes: map[time.Duration]preloadTimes{},
+		storage:            storage,
 	}
 }
 
+func (analyzer *QueryAnalyzer) getPreloadTimes(offset time.Duration) preloadTimes {
+	if _, ok := analyzer.offsetPreloadTimes[offset]; !ok {
+		analyzer.offsetPreloadTimes[offset] = preloadTimes{
+			instants: map[clientmodel.Fingerprint]struct{}{},
+			ranges:   map[clientmodel.Fingerprint]time.Duration{},
+		}
+	}
+	return analyzer.offsetPreloadTimes[offset]
+}
+
 // Visit implements the Visitor interface.
 func (analyzer *QueryAnalyzer) Visit(node Node) {
 	switch n := node.(type) {
 	case *VectorSelector:
+		pt := analyzer.getPreloadTimes(n.offset)
 		fingerprints := analyzer.storage.GetFingerprintsForLabelMatchers(n.labelMatchers)
 		n.fingerprints = fingerprints
 		for _, fp := range fingerprints {
-			// Only add the fingerprint to IntervalRanges if not yet present in FullRanges.
-			// Full ranges always contain more points and span more time than interval ranges.
-			if _, alreadyInFullRanges := analyzer.FullRanges[fp]; !alreadyInFullRanges {
-				analyzer.IntervalRanges[fp] = true
+			// Only add the fingerprint to the instants if not yet present in the
+			// ranges. Ranges always contain more points and span more time than
+			// instants for the same offset.
+			if _, alreadyInRanges := pt.ranges[fp]; !alreadyInRanges {
+				pt.instants[fp] = struct{}{}
 			}
 
 			n.metrics[fp] = analyzer.storage.GetMetricForFingerprint(fp)
 		}
 	case *MatrixSelector:
+		pt := analyzer.getPreloadTimes(n.offset)
 		fingerprints := analyzer.storage.GetFingerprintsForLabelMatchers(n.labelMatchers)
 		n.fingerprints = fingerprints
 		for _, fp := range fingerprints {
-			if analyzer.FullRanges[fp] < n.interval {
-				analyzer.FullRanges[fp] = n.interval
-				// Delete the fingerprint from IntervalRanges. Full ranges always contain
-				// more points and span more time than interval ranges, so we don't need
-				// an interval range for the same fingerprint, should we have one.
-				delete(analyzer.IntervalRanges, fp)
+			if pt.ranges[fp] < n.interval {
+				pt.ranges[fp] = n.interval
+				// Delete the fingerprint from the instants. Ranges always contain more
+				// points and span more time than instants, so we don't need to track
+				// an instant for the same fingerprint, should we have one.
+				delete(pt.instants, fp)
 			}
 
 			n.metrics[fp] = analyzer.storage.GetMetricForFingerprint(fp)
@@ -119,28 +130,31 @@ func prepareInstantQuery(node Node, timestamp clientmodel.Timestamp, storage loc
 
 	preloadTimer := queryStats.GetTimer(stats.PreloadTime).Start()
 	p := storage.NewPreloader()
-	for fp, rangeDuration := range analyzer.FullRanges {
-		if et := totalTimer.ElapsedTime(); et > *queryTimeout {
-			preloadTimer.Stop()
-			p.Close()
-			return nil, queryTimeoutError{et}
+	for offset, pt := range analyzer.offsetPreloadTimes {
+		ts := timestamp.Add(-offset)
+		for fp, rangeDuration := range pt.ranges {
+			if et := totalTimer.ElapsedTime(); et > *queryTimeout {
+				preloadTimer.Stop()
+				p.Close()
+				return nil, queryTimeoutError{et}
+			}
+			if err := p.PreloadRange(fp, ts.Add(-rangeDuration), ts, *stalenessDelta); err != nil {
+				preloadTimer.Stop()
+				p.Close()
+				return nil, err
+			}
 		}
-		if err := p.PreloadRange(fp, timestamp.Add(-rangeDuration), timestamp, *stalenessDelta); err != nil {
-			preloadTimer.Stop()
-			p.Close()
-			return nil, err
-		}
-	}
-	for fp := range analyzer.IntervalRanges {
-		if et := totalTimer.ElapsedTime(); et > *queryTimeout {
-			preloadTimer.Stop()
-			p.Close()
-			return nil, queryTimeoutError{et}
-		}
-		if err := p.PreloadRange(fp, timestamp, timestamp, *stalenessDelta); err != nil {
-			preloadTimer.Stop()
-			p.Close()
-			return nil, err
+		for fp := range pt.instants {
+			if et := totalTimer.ElapsedTime(); et > *queryTimeout {
+				preloadTimer.Stop()
+				p.Close()
+				return nil, queryTimeoutError{et}
+			}
+			if err := p.PreloadRange(fp, ts, ts, *stalenessDelta); err != nil {
+				preloadTimer.Stop()
+				p.Close()
+				return nil, err
+			}
 		}
 	}
 	preloadTimer.Stop()
@@ -163,41 +177,45 @@ func prepareRangeQuery(node Node, start clientmodel.Timestamp, end clientmodel.T
 
 	preloadTimer := queryStats.GetTimer(stats.PreloadTime).Start()
 	p := storage.NewPreloader()
-	for fp, rangeDuration := range analyzer.FullRanges {
-		if et := totalTimer.ElapsedTime(); et > *queryTimeout {
-			preloadTimer.Stop()
-			p.Close()
-			return nil, queryTimeoutError{et}
-		}
-		if err := p.PreloadRange(fp, start.Add(-rangeDuration), end, *stalenessDelta); err != nil {
-			preloadTimer.Stop()
-			p.Close()
-			return nil, err
-		}
-		/*
-			if interval < rangeDuration {
-				if err := p.GetMetricRange(fp, end, end.Sub(start)+rangeDuration); err != nil {
-					p.Close()
-					return nil, err
-				}
-			} else {
-				if err := p.GetMetricRangeAtInterval(fp, start, end, interval, rangeDuration); err != nil {
-					p.Close()
-					return nil, err
-				}
+	for offset, pt := range analyzer.offsetPreloadTimes {
+		offsetStart := start.Add(-offset)
+		offsetEnd := end.Add(-offset)
+		for fp, rangeDuration := range pt.ranges {
+			if et := totalTimer.ElapsedTime(); et > *queryTimeout {
+				preloadTimer.Stop()
+				p.Close()
+				return nil, queryTimeoutError{et}
 			}
-		*/
-	}
-	for fp := range analyzer.IntervalRanges {
-		if et := totalTimer.ElapsedTime(); et > *queryTimeout {
-			preloadTimer.Stop()
-			p.Close()
-			return nil, queryTimeoutError{et}
+			if err := p.PreloadRange(fp, offsetStart.Add(-rangeDuration), offsetEnd, *stalenessDelta); err != nil {
+				preloadTimer.Stop()
+				p.Close()
+				return nil, err
+			}
+			/*
+				if interval < rangeDuration {
+					if err := p.GetMetricRange(fp, offsetEnd, offsetEnd.Sub(offsetStart)+rangeDuration); err != nil {
+						p.Close()
+						return nil, err
+					}
+				} else {
+					if err := p.GetMetricRangeAtInterval(fp, offsetStart, offsetEnd, interval, rangeDuration); err != nil {
+						p.Close()
+						return nil, err
+					}
+				}
+			*/
 		}
-		if err := p.PreloadRange(fp, start, end, *stalenessDelta); err != nil {
-			preloadTimer.Stop()
-			p.Close()
-			return nil, err
+		for fp := range pt.instants {
+			if et := totalTimer.ElapsedTime(); et > *queryTimeout {
+				preloadTimer.Stop()
+				p.Close()
+				return nil, queryTimeoutError{et}
+			}
+			if err := p.PreloadRange(fp, offsetStart, offsetEnd, *stalenessDelta); err != nil {
+				preloadTimer.Stop()
+				p.Close()
+				return nil, err
+			}
 		}
 	}
 	preloadTimer.Stop()
diff --git a/rules/helpers.go b/rules/helpers.go
index aa80a9e8ea..1c6287dc16 100644
--- a/rules/helpers.go
+++ b/rules/helpers.go
@@ -110,22 +110,30 @@ func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node) (ast.Node, error
 	return expr, nil
 }
 
-// NewMatrixSelector is a convenience function to create a new AST matrix selector.
-func NewMatrixSelector(vector ast.Node, intervalStr string) (ast.MatrixNode, error) {
-	switch vector.(type) {
-	case *ast.VectorSelector:
-		{
-			break
-		}
-	default:
-		return nil, fmt.Errorf("intervals are currently only supported for vector selectors")
+// NewVectorSelector is a convenience function to create a new AST vector selector.
+func NewVectorSelector(m metric.LabelMatchers, offsetStr string) (ast.VectorNode, error) {
+	offset, err := utility.StringToDuration(offsetStr)
+	if err != nil {
+		return nil, err
 	}
+	return ast.NewVectorSelector(m, offset), nil
+}
+
+// NewMatrixSelector is a convenience function to create a new AST matrix selector.
+func NewMatrixSelector(vector ast.Node, intervalStr string, offsetStr string) (ast.MatrixNode, error) {
 	interval, err := utility.StringToDuration(intervalStr)
 	if err != nil {
 		return nil, err
 	}
-	vectorSelector := vector.(*ast.VectorSelector)
-	return ast.NewMatrixSelector(vectorSelector, interval), nil
+	offset, err := utility.StringToDuration(offsetStr)
+	if err != nil {
+		return nil, err
+	}
+	vectorSelector, ok := vector.(*ast.VectorSelector)
+	if !ok {
+		return nil, fmt.Errorf("intervals are currently only supported for vector selectors")
+	}
+	return ast.NewMatrixSelector(vectorSelector, interval, offset), nil
 }
 
 func newLabelMatcher(matchTypeStr string, name clientmodel.LabelName, value clientmodel.LabelValue) (*metric.LabelMatcher, error) {
diff --git a/rules/lexer.l b/rules/lexer.l
index 83577f175a..82c900cff3 100644
--- a/rules/lexer.l
+++ b/rules/lexer.l
@@ -80,6 +80,7 @@ DESCRIPTION|description  return DESCRIPTION
 PERMANENT|permanent      return PERMANENT
 BY|by                    return GROUP_OP
 KEEPING_EXTRA|keeping_extra return KEEPING_EXTRA
+OFFSET|offset            return OFFSET
 AVG|SUM|MAX|MIN|COUNT    lval.str = lexer.token(); return AGGR_OP
 avg|sum|max|min|count    lval.str = strings.ToUpper(lexer.token()); return AGGR_OP
 \<|>|AND|OR|and|or       lval.str = strings.ToUpper(lexer.token()); return CMP_OP
diff --git a/rules/lexer.l.go b/rules/lexer.l.go
index d8a1d42f93..82706f65a1 100644
--- a/rules/lexer.l.go
+++ b/rules/lexer.l.go
@@ -56,7 +56,7 @@ yystate0:
 	case 0: // start condition: INITIAL
 		goto yystart1
 	case 1: // start condition: S_COMMENTS
-		goto yystart153
+		goto yystart162
 	}
 
 	goto yystate0 // silence unused label error
@@ -108,46 +108,46 @@ yystart1:
 	case c == 'O':
 		goto yystate74
 	case c == 'P':
-		goto yystate75
+		goto yystate80
 	case c == 'S':
-		goto yystate84
+		goto yystate89
 	case c == 'W':
-		goto yystate91
+		goto yystate96
 	case c == '\'':
 		goto yystate9
 	case c == '\t' || c == '\n' || c == '\r' || c == ' ':
 		goto yystate2
 	case c == 'a':
-		goto yystate95
+		goto yystate100
 	case c == 'b':
-		goto yystate102
-	case c == 'c':
-		goto yystate103
-	case c == 'd':
 		goto yystate107
+	case c == 'c':
+		goto yystate108
+	case c == 'd':
+		goto yystate112
 	case c == 'f':
-		goto yystate117
+		goto yystate122
 	case c == 'i':
-		goto yystate119
+		goto yystate124
 	case c == 'k':
-		goto yystate120
+		goto yystate125
 	case c == 'm':
-		goto yystate132
+		goto yystate137
 	case c == 'o':
-		goto yystate135
+		goto yystate140
 	case c == 'p':
-		goto yystate136
+		goto yystate145
 	case c == 's':
-		goto yystate144
+		goto yystate153
 	case c == 'w':
-		goto yystate150
+		goto yystate159
 	case c >= '0' && c <= '9':
 		goto yystate21
 	}
 
 yystate2:
 	c = lexer.getChar()
-	goto yyrule27
+	goto yyrule28
 
 yystate3:
 	c = lexer.getChar()
@@ -160,7 +160,7 @@ yystate3:
 
 yystate4:
 	c = lexer.getChar()
-	goto yyrule17
+	goto yyrule18
 
 yystate5:
 	c = lexer.getChar()
@@ -177,7 +177,7 @@ yystate5:
 
 yystate6:
 	c = lexer.getChar()
-	goto yyrule24
+	goto yyrule25
 
 yystate7:
 	c = lexer.getChar()
@@ -190,7 +190,7 @@ yystate7:
 
 yystate8:
 	c = lexer.getChar()
-	goto yyrule19
+	goto yyrule20
 
 yystate9:
 	c = lexer.getChar()
@@ -207,7 +207,7 @@ yystate9:
 
 yystate10:
 	c = lexer.getChar()
-	goto yyrule25
+	goto yyrule26
 
 yystate11:
 	c = lexer.getChar()
@@ -220,17 +220,17 @@ yystate11:
 
 yystate12:
 	c = lexer.getChar()
-	goto yyrule26
+	goto yyrule27
 
 yystate13:
 	c = lexer.getChar()
-	goto yyrule18
+	goto yyrule19
 
 yystate14:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule18
+		goto yyrule19
 	case c >= '0' && c <= '9':
 		goto yystate15
 	}
@@ -239,7 +239,7 @@ yystate15:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule23
+		goto yyrule24
 	case c == '.':
 		goto yystate16
 	case c >= '0' && c <= '9':
@@ -250,7 +250,7 @@ yystate16:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule23
+		goto yyrule24
 	case c >= '0' && c <= '9':
 		goto yystate16
 	}
@@ -259,7 +259,7 @@ yystate17:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule19
+		goto yyrule20
 	case c == '*':
 		goto yystate18
 	case c == '/':
@@ -289,7 +289,7 @@ yystate21:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule23
+		goto yyrule24
 	case c == '.':
 		goto yystate16
 	case c == 'd' || c == 'h' || c == 'm' || c == 's' || c == 'w' || c == 'y':
@@ -300,13 +300,13 @@ yystate21:
 
 yystate22:
 	c = lexer.getChar()
-	goto yyrule20
+	goto yyrule21
 
 yystate23:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule22
+		goto yyrule23
 	case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate23
 	}
@@ -315,7 +315,7 @@ yystate24:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule16
+		goto yyrule17
 	case c == '=':
 		goto yystate4
 	}
@@ -324,7 +324,7 @@ yystate25:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule26
+		goto yyrule27
 	case c == '=' || c == '~':
 		goto yystate4
 	}
@@ -333,7 +333,7 @@ yystate26:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'L':
@@ -350,7 +350,7 @@ yystate27:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
@@ -361,7 +361,7 @@ yystate28:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'E':
@@ -374,7 +374,7 @@ yystate29:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'R':
@@ -387,7 +387,7 @@ yystate30:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'T':
@@ -411,7 +411,7 @@ yystate32:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'D':
@@ -424,7 +424,7 @@ yystate33:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule16
+		goto yyrule17
 	case c == ':':
 		goto yystate23
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
@@ -435,7 +435,7 @@ yystate34:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'G':
@@ -448,7 +448,7 @@ yystate35:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule14
+		goto yyrule15
 	case c == ':':
 		goto yystate23
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
@@ -459,7 +459,7 @@ yystate36:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'Y':
@@ -483,7 +483,7 @@ yystate38:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'O':
@@ -496,7 +496,7 @@ yystate39:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'U':
@@ -509,7 +509,7 @@ yystate40:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'N':
@@ -522,7 +522,7 @@ yystate41:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'T':
@@ -535,7 +535,7 @@ yystate42:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'E':
@@ -548,7 +548,7 @@ yystate43:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'S':
@@ -561,7 +561,7 @@ yystate44:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'C':
@@ -574,7 +574,7 @@ yystate45:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'R':
@@ -587,7 +587,7 @@ yystate46:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'I':
@@ -600,7 +600,7 @@ yystate47:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'P':
@@ -613,7 +613,7 @@ yystate48:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'T':
@@ -626,7 +626,7 @@ yystate49:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'I':
@@ -639,7 +639,7 @@ yystate50:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'O':
@@ -652,7 +652,7 @@ yystate51:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'N':
@@ -676,7 +676,7 @@ yystate53:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'O':
@@ -689,7 +689,7 @@ yystate54:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'R':
@@ -713,7 +713,7 @@ yystate56:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'F':
@@ -737,7 +737,7 @@ yystate58:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'E':
@@ -750,7 +750,7 @@ yystate59:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'E':
@@ -763,7 +763,7 @@ yystate60:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'P':
@@ -776,7 +776,7 @@ yystate61:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'I':
@@ -789,7 +789,7 @@ yystate62:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'N':
@@ -802,7 +802,7 @@ yystate63:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'G':
@@ -815,7 +815,7 @@ yystate64:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == '_':
@@ -828,7 +828,7 @@ yystate65:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'E':
@@ -841,7 +841,7 @@ yystate66:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'X':
@@ -854,7 +854,7 @@ yystate67:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'T':
@@ -867,7 +867,7 @@ yystate68:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'R':
@@ -880,7 +880,7 @@ yystate69:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'A':
@@ -904,7 +904,7 @@ yystate71:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'A':
@@ -919,7 +919,7 @@ yystate72:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'X':
@@ -932,7 +932,7 @@ yystate73:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'N':
@@ -945,12 +945,14 @@ yystate74:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
+	case c == 'F':
+		goto yystate75
 	case c == 'R':
 		goto yystate33
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'E' || c >= 'G' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
@@ -958,12 +960,12 @@ yystate75:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'E':
+	case c == 'F':
 		goto yystate76
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'E' || c >= 'G' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
@@ -971,12 +973,12 @@ yystate76:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'R':
+	case c == 'S':
 		goto yystate77
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'R' || c >= 'T' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
@@ -984,12 +986,12 @@ yystate77:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'M':
+	case c == 'E':
 		goto yystate78
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
@@ -997,12 +999,12 @@ yystate78:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'A':
+	case c == 'T':
 		goto yystate79
-	case c >= '0' && c <= '9' || c >= 'B' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1010,12 +1012,10 @@ yystate79:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule14
 	case c == ':':
 		goto yystate23
-	case c == 'N':
-		goto yystate80
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1023,7 +1023,7 @@ yystate80:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'E':
@@ -1036,12 +1036,12 @@ yystate81:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'N':
+	case c == 'R':
 		goto yystate82
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1049,16 +1049,81 @@ yystate82:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'T':
+	case c == 'M':
 		goto yystate83
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
 yystate83:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'A':
+		goto yystate84
+	case c >= '0' && c <= '9' || c >= 'B' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate84:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'N':
+		goto yystate85
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate85:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'E':
+		goto yystate86
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'D' || c >= 'F' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate86:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'N':
+		goto yystate87
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'M' || c >= 'O' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate87:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'T':
+		goto yystate88
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate88:
 	c = lexer.getChar()
 	switch {
 	default:
@@ -1069,85 +1134,85 @@ yystate83:
 		goto yystate27
 	}
 
-yystate84:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'U':
-		goto yystate85
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'T' || c >= 'V' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
-		goto yystate27
-	}
-
-yystate85:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'M':
-		goto yystate86
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
-		goto yystate27
-	}
-
-yystate86:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule14
-	case c == ':':
-		goto yystate23
-	case c == 'M':
-		goto yystate87
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
-		goto yystate27
-	}
-
-yystate87:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'A':
-		goto yystate88
-	case c >= '0' && c <= '9' || c >= 'B' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
-		goto yystate27
-	}
-
-yystate88:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'R':
-		goto yystate89
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
-		goto yystate27
-	}
-
 yystate89:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'Y':
+	case c == 'U':
 		goto yystate90
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'X' || c == 'Z' || c == '_' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'T' || c >= 'V' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
 yystate90:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'M':
+		goto yystate91
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate91:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule15
+	case c == ':':
+		goto yystate23
+	case c == 'M':
+		goto yystate92
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'L' || c >= 'N' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate92:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'A':
+		goto yystate93
+	case c >= '0' && c <= '9' || c >= 'B' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate93:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'R':
+		goto yystate94
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Q' || c >= 'S' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate94:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'Y':
+		goto yystate95
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'X' || c == 'Z' || c == '_' || c >= 'a' && c <= 'z':
+		goto yystate27
+	}
+
+yystate95:
 	c = lexer.getChar()
 	switch {
 	default:
@@ -1158,46 +1223,46 @@ yystate90:
 		goto yystate27
 	}
 
-yystate91:
+yystate96:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'I':
-		goto yystate92
+		goto yystate97
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'H' || c >= 'J' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
-yystate92:
+yystate97:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'T':
-		goto yystate93
+		goto yystate98
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'S' || c >= 'U' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
-yystate93:
+yystate98:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'H':
-		goto yystate94
+		goto yystate99
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'G' || c >= 'I' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
-yystate94:
+yystate99:
 	c = lexer.getChar()
 	switch {
 	default:
@@ -1208,54 +1273,54 @@ yystate94:
 		goto yystate27
 	}
 
-yystate95:
+yystate100:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'l':
-		goto yystate96
+		goto yystate101
 	case c == 'n':
-		goto yystate99
+		goto yystate104
 	case c == 'v':
-		goto yystate100
+		goto yystate105
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'k' || c == 'm' || c >= 'o' && c <= 'u' || c >= 'w' && c <= 'z':
 		goto yystate27
 	}
 
-yystate96:
+yystate101:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'e':
-		goto yystate97
+		goto yystate102
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
 		goto yystate27
 	}
 
-yystate97:
+yystate102:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'r':
-		goto yystate98
+		goto yystate103
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
 		goto yystate27
 	}
 
-yystate98:
+yystate103:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 't':
@@ -1264,11 +1329,11 @@ yystate98:
 		goto yystate27
 	}
 
-yystate99:
+yystate104:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'd':
@@ -1277,35 +1342,35 @@ yystate99:
 		goto yystate27
 	}
 
-yystate100:
+yystate105:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'g':
-		goto yystate101
+		goto yystate106
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'f' || c >= 'h' && c <= 'z':
 		goto yystate27
 	}
 
-yystate101:
+yystate106:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule15
+		goto yyrule16
 	case c == ':':
 		goto yystate23
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
-yystate102:
+yystate107:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'y':
@@ -1314,81 +1379,16 @@ yystate102:
 		goto yystate27
 	}
 
-yystate103:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'o':
-		goto yystate104
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z':
-		goto yystate27
-	}
-
-yystate104:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'u':
-		goto yystate105
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 't' || c >= 'v' && c <= 'z':
-		goto yystate27
-	}
-
-yystate105:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'n':
-		goto yystate106
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
-		goto yystate27
-	}
-
-yystate106:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 't':
-		goto yystate101
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
-		goto yystate27
-	}
-
-yystate107:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'e':
-		goto yystate108
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
-		goto yystate27
-	}
-
 yystate108:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 's':
+	case c == 'o':
 		goto yystate109
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'r' || c >= 't' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1396,12 +1396,12 @@ yystate109:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'c':
+	case c == 'u':
 		goto yystate110
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c == 'a' || c == 'b' || c >= 'd' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 't' || c >= 'v' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1409,12 +1409,12 @@ yystate110:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'r':
+	case c == 'n':
 		goto yystate111
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1422,12 +1422,12 @@ yystate111:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'i':
-		goto yystate112
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z':
+	case c == 't':
+		goto yystate106
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1435,12 +1435,12 @@ yystate112:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'p':
+	case c == 'e':
 		goto yystate113
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'o' || c >= 'q' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1448,12 +1448,12 @@ yystate113:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 't':
+	case c == 's':
 		goto yystate114
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'r' || c >= 't' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1461,12 +1461,12 @@ yystate114:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'i':
+	case c == 'c':
 		goto yystate115
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c == 'a' || c == 'b' || c >= 'd' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1474,12 +1474,12 @@ yystate115:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'o':
+	case c == 'r':
 		goto yystate116
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1487,7 +1487,72 @@ yystate116:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'i':
+		goto yystate117
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z':
+		goto yystate27
+	}
+
+yystate117:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'p':
+		goto yystate118
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'o' || c >= 'q' && c <= 'z':
+		goto yystate27
+	}
+
+yystate118:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 't':
+		goto yystate119
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
+		goto yystate27
+	}
+
+yystate119:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'i':
+		goto yystate120
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z':
+		goto yystate27
+	}
+
+yystate120:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'o':
+		goto yystate121
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z':
+		goto yystate27
+	}
+
+yystate121:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'n':
@@ -1496,24 +1561,24 @@ yystate116:
 		goto yystate27
 	}
 
-yystate117:
+yystate122:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'o':
-		goto yystate118
+		goto yystate123
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'n' || c >= 'p' && c <= 'z':
 		goto yystate27
 	}
 
-yystate118:
+yystate123:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'r':
@@ -1522,11 +1587,11 @@ yystate118:
 		goto yystate27
 	}
 
-yystate119:
+yystate124:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'f':
@@ -1535,81 +1600,16 @@ yystate119:
 		goto yystate27
 	}
 
-yystate120:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'e':
-		goto yystate121
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
-		goto yystate27
-	}
-
-yystate121:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'e':
-		goto yystate122
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
-		goto yystate27
-	}
-
-yystate122:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'p':
-		goto yystate123
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'o' || c >= 'q' && c <= 'z':
-		goto yystate27
-	}
-
-yystate123:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'i':
-		goto yystate124
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z':
-		goto yystate27
-	}
-
-yystate124:
-	c = lexer.getChar()
-	switch {
-	default:
-		goto yyrule21
-	case c == ':':
-		goto yystate23
-	case c == 'n':
-		goto yystate125
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
-		goto yystate27
-	}
-
 yystate125:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'g':
+	case c == 'e':
 		goto yystate126
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'f' || c >= 'h' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1617,12 +1617,12 @@ yystate126:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == '_':
+	case c == 'e':
 		goto yystate127
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1630,12 +1630,12 @@ yystate127:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'e':
+	case c == 'p':
 		goto yystate128
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'o' || c >= 'q' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1643,12 +1643,12 @@ yystate128:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'x':
+	case c == 'i':
 		goto yystate129
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'w' || c == 'y' || c == 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1656,12 +1656,12 @@ yystate129:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 't':
+	case c == 'n':
 		goto yystate130
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1669,12 +1669,12 @@ yystate130:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'r':
+	case c == 'g':
 		goto yystate131
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'f' || c >= 'h' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1682,12 +1682,12 @@ yystate131:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'a':
-		goto yystate70
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z':
+	case c == '_':
+		goto yystate132
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1695,14 +1695,12 @@ yystate132:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'a':
+	case c == 'e':
 		goto yystate133
-	case c == 'i':
-		goto yystate134
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'h' || c >= 'j' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1710,11 +1708,11 @@ yystate133:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'x':
-		goto yystate101
+		goto yystate134
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'w' || c == 'y' || c == 'z':
 		goto yystate27
 	}
@@ -1723,12 +1721,12 @@ yystate134:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'n':
-		goto yystate101
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
+	case c == 't':
+		goto yystate135
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1736,11 +1734,11 @@ yystate135:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
 	case c == 'r':
-		goto yystate33
+		goto yystate136
 	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
 		goto yystate27
 	}
@@ -1749,12 +1747,12 @@ yystate136:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'e':
-		goto yystate137
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
+	case c == 'a':
+		goto yystate70
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1762,12 +1760,14 @@ yystate137:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'r':
+	case c == 'a':
 		goto yystate138
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
+	case c == 'i':
+		goto yystate139
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'h' || c >= 'j' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1775,12 +1775,12 @@ yystate138:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'm':
-		goto yystate139
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z':
+	case c == 'x':
+		goto yystate106
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'w' || c == 'y' || c == 'z':
 		goto yystate27
 	}
 
@@ -1788,12 +1788,12 @@ yystate139:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'a':
-		goto yystate140
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z':
+	case c == 'n':
+		goto yystate106
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1801,12 +1801,14 @@ yystate140:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'n':
+	case c == 'f':
 		goto yystate141
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
+	case c == 'r':
+		goto yystate33
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'e' || c >= 'g' && c <= 'q' || c >= 's' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1814,12 +1816,12 @@ yystate141:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'e':
+	case c == 'f':
 		goto yystate142
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'e' || c >= 'g' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1827,12 +1829,12 @@ yystate142:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'n':
+	case c == 's':
 		goto yystate143
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'r' || c >= 't' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1840,12 +1842,12 @@ yystate143:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 't':
-		goto yystate83
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
+	case c == 'e':
+		goto yystate144
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1853,12 +1855,12 @@ yystate144:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'u':
-		goto yystate145
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 't' || c >= 'v' && c <= 'z':
+	case c == 't':
+		goto yystate79
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1866,12 +1868,12 @@ yystate145:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'm':
+	case c == 'e':
 		goto yystate146
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1879,12 +1881,12 @@ yystate146:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule15
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'm':
+	case c == 'r':
 		goto yystate147
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1892,12 +1894,12 @@ yystate147:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'a':
+	case c == 'm':
 		goto yystate148
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1905,12 +1907,12 @@ yystate148:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'r':
+	case c == 'a':
 		goto yystate149
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1918,12 +1920,12 @@ yystate149:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'y':
-		goto yystate90
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'x' || c == 'z':
+	case c == 'n':
+		goto yystate150
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1931,12 +1933,12 @@ yystate150:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'i':
+	case c == 'e':
 		goto yystate151
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'd' || c >= 'f' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1944,12 +1946,12 @@ yystate151:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 't':
+	case c == 'n':
 		goto yystate152
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'm' || c >= 'o' && c <= 'z':
 		goto yystate27
 	}
 
@@ -1957,42 +1959,159 @@ yystate152:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule21
+		goto yyrule22
 	case c == ':':
 		goto yystate23
-	case c == 'h':
-		goto yystate94
-	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'g' || c >= 'i' && c <= 'z':
+	case c == 't':
+		goto yystate88
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
 		goto yystate27
 	}
 
-	goto yystate153 // silence unused label error
 yystate153:
 	c = lexer.getChar()
-yystart153:
 	switch {
 	default:
-		goto yyabort
-	case c == '*':
-		goto yystate155
-	case c >= '\x01' && c <= ')' || c >= '+' && c <= 'ÿ':
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'u':
 		goto yystate154
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 't' || c >= 'v' && c <= 'z':
+		goto yystate27
 	}
 
 yystate154:
 	c = lexer.getChar()
-	goto yyrule3
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'm':
+		goto yystate155
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z':
+		goto yystate27
+	}
 
 yystate155:
 	c = lexer.getChar()
 	switch {
 	default:
-		goto yyrule3
-	case c == '/':
+		goto yyrule16
+	case c == ':':
+		goto yystate23
+	case c == 'm':
 		goto yystate156
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'l' || c >= 'n' && c <= 'z':
+		goto yystate27
 	}
 
 yystate156:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'a':
+		goto yystate157
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'b' && c <= 'z':
+		goto yystate27
+	}
+
+yystate157:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'r':
+		goto yystate158
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'q' || c >= 's' && c <= 'z':
+		goto yystate27
+	}
+
+yystate158:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'y':
+		goto yystate95
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'x' || c == 'z':
+		goto yystate27
+	}
+
+yystate159:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'i':
+		goto yystate160
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'h' || c >= 'j' && c <= 'z':
+		goto yystate27
+	}
+
+yystate160:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 't':
+		goto yystate161
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 's' || c >= 'u' && c <= 'z':
+		goto yystate27
+	}
+
+yystate161:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule22
+	case c == ':':
+		goto yystate23
+	case c == 'h':
+		goto yystate99
+	case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'g' || c >= 'i' && c <= 'z':
+		goto yystate27
+	}
+
+	goto yystate162 // silence unused label error
+yystate162:
+	c = lexer.getChar()
+yystart162:
+	switch {
+	default:
+		goto yyabort
+	case c == '*':
+		goto yystate164
+	case c >= '\x01' && c <= ')' || c >= '+' && c <= 'ÿ':
+		goto yystate163
+	}
+
+yystate163:
+	c = lexer.getChar()
+	goto yyrule3
+
+yystate164:
+	c = lexer.getChar()
+	switch {
+	default:
+		goto yyrule3
+	case c == '/':
+		goto yystate165
+	}
+
+yystate165:
 	c = lexer.getChar()
 	goto yyrule2
 
@@ -2052,61 +2171,65 @@ yyrule13: // KEEPING_EXTRA|keeping_extra
 	{
 		return KEEPING_EXTRA
 	}
-yyrule14: // AVG|SUM|MAX|MIN|COUNT
+yyrule14: // OFFSET|offset
+	{
+		return OFFSET
+	}
+yyrule15: // AVG|SUM|MAX|MIN|COUNT
 	{
 		lval.str = lexer.token()
 		return AGGR_OP
 		goto yystate0
 	}
-yyrule15: // avg|sum|max|min|count
+yyrule16: // avg|sum|max|min|count
 	{
 		lval.str = strings.ToUpper(lexer.token())
 		return AGGR_OP
 		goto yystate0
 	}
-yyrule16: // \<|>|AND|OR|and|or
+yyrule17: // \<|>|AND|OR|and|or
 	{
 		lval.str = strings.ToUpper(lexer.token())
 		return CMP_OP
 		goto yystate0
 	}
-yyrule17: // ==|!=|>=|<=|=~|!~
+yyrule18: // ==|!=|>=|<=|=~|!~
 	{
 		lval.str = lexer.token()
 		return CMP_OP
 		goto yystate0
 	}
-yyrule18: // [+\-]
+yyrule19: // [+\-]
 	{
 		lval.str = lexer.token()
 		return ADDITIVE_OP
 		goto yystate0
 	}
-yyrule19: // [*/%]
+yyrule20: // [*/%]
 	{
 		lval.str = lexer.token()
 		return MULT_OP
 		goto yystate0
 	}
-yyrule20: // {D}+{U}
+yyrule21: // {D}+{U}
 	{
 		lval.str = lexer.token()
 		return DURATION
 		goto yystate0
 	}
-yyrule21: // {L}({L}|{D})*
+yyrule22: // {L}({L}|{D})*
 	{
 		lval.str = lexer.token()
 		return IDENTIFIER
 		goto yystate0
 	}
-yyrule22: // {M}({M}|{D})*
+yyrule23: // {M}({M}|{D})*
 	{
 		lval.str = lexer.token()
 		return METRICNAME
 		goto yystate0
 	}
-yyrule23: // \-?{D}+(\.{D}*)?
+yyrule24: // \-?{D}+(\.{D}*)?
 	{
 		num, err := strconv.ParseFloat(lexer.token(), 64)
 		if err != nil && err.(*strconv.NumError).Err == strconv.ErrSyntax {
@@ -2115,23 +2238,23 @@ yyrule23: // \-?{D}+(\.{D}*)?
 		lval.num = clientmodel.SampleValue(num)
 		return NUMBER
 	}
-yyrule24: // \"(\\.|[^\\"])*\"
+yyrule25: // \"(\\.|[^\\"])*\"
 	{
 		lval.str = lexer.token()[1 : len(lexer.token())-1]
 		return STRING
 		goto yystate0
 	}
-yyrule25: // \'(\\.|[^\\'])*\'
+yyrule26: // \'(\\.|[^\\'])*\'
 	{
 		lval.str = lexer.token()[1 : len(lexer.token())-1]
 		return STRING
 		goto yystate0
 	}
-yyrule26: // [{}\[\]()=,]
+yyrule27: // [{}\[\]()=,]
 	{
 		return int(lexer.buf[0])
 	}
-yyrule27: // [\t\n\r ]
+yyrule28: // [\t\n\r ]
 	{
 		/* gobble up any whitespace */
 		goto yystate0
diff --git a/rules/parser.y b/rules/parser.y
index af6e069410..b0c11c1b98 100644
--- a/rules/parser.y
+++ b/rules/parser.y
@@ -42,7 +42,7 @@
 
 %token <str> IDENTIFIER STRING DURATION METRICNAME
 %token <num> NUMBER
-%token PERMANENT GROUP_OP KEEPING_EXTRA
+%token PERMANENT GROUP_OP KEEPING_EXTRA OFFSET
 %token <str> AGGR_OP CMP_OP ADDITIVE_OP MULT_OP
 %token ALERT IF FOR WITH SUMMARY DESCRIPTION
 
@@ -53,7 +53,7 @@
 %type <labelMatchers> label_match_list label_matches
 %type <ruleNode> rule_expr func_arg
 %type <boolean> qualifier extra_labels_opts
-%type <str> for_duration metric_name label_match_type
+%type <str> for_duration metric_name label_match_type offset_opts
 
 %right '='
 %left CMP_OP
@@ -152,17 +152,28 @@ label_match_type   : '='
                      { $$ = $1 }
                    ;
 
+offset_opts        : /* empty */
+                     { $$ = "0s" }
+                   | OFFSET DURATION
+                     { $$ = $2 }
+                   ;
+
 rule_expr          : '(' rule_expr ')'
                      { $$ = $2 }
-                   | '{' label_match_list '}'
-                     { $$ = ast.NewVectorSelector($2) }
-                   | metric_name label_matches
+                   | '{' label_match_list '}' offset_opts
+                     {
+                       var err error
+                       $$, err = NewVectorSelector($2, $4)
+                       if err != nil { yylex.Error(err.Error()); return 1 }
+                     }
+                   | metric_name label_matches offset_opts
                      {
                        var err error
                        m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue($1))
                        if err != nil { yylex.Error(err.Error()); return 1 }
                        $2 = append($2, m)
-                       $$ = ast.NewVectorSelector($2)
+                       $$, err = NewVectorSelector($2, $3)
+                       if err != nil { yylex.Error(err.Error()); return 1 }
                      }
                    | IDENTIFIER '(' func_arg_list ')'
                      {
@@ -176,10 +187,10 @@ rule_expr          : '(' rule_expr ')'
                        $$, err = NewFunctionCall($1, []ast.Node{})
                        if err != nil { yylex.Error(err.Error()); return 1 }
                      }
-                   | rule_expr '[' DURATION ']'
+                   | rule_expr '[' DURATION ']' offset_opts
                      {
                        var err error
-                       $$, err = NewMatrixSelector($1, $3)
+                       $$, err = NewMatrixSelector($1, $3, $5)
                        if err != nil { yylex.Error(err.Error()); return 1 }
                      }
                    | AGGR_OP '(' rule_expr ')' grouping_opts extra_labels_opts
diff --git a/rules/parser.y.go b/rules/parser.y.go
index e70ab4b154..e69abcd6af 100644
--- a/rules/parser.y.go
+++ b/rules/parser.y.go
@@ -35,16 +35,17 @@ const NUMBER = 57352
 const PERMANENT = 57353
 const GROUP_OP = 57354
 const KEEPING_EXTRA = 57355
-const AGGR_OP = 57356
-const CMP_OP = 57357
-const ADDITIVE_OP = 57358
-const MULT_OP = 57359
-const ALERT = 57360
-const IF = 57361
-const FOR = 57362
-const WITH = 57363
-const SUMMARY = 57364
-const DESCRIPTION = 57365
+const OFFSET = 57356
+const AGGR_OP = 57357
+const CMP_OP = 57358
+const ADDITIVE_OP = 57359
+const MULT_OP = 57360
+const ALERT = 57361
+const IF = 57362
+const FOR = 57363
+const WITH = 57364
+const SUMMARY = 57365
+const DESCRIPTION = 57366
 
 var yyToknames = []string{
 	"START_RULES",
@@ -57,6 +58,7 @@ var yyToknames = []string{
 	"PERMANENT",
 	"GROUP_OP",
 	"KEEPING_EXTRA",
+	"OFFSET",
 	"AGGR_OP",
 	"CMP_OP",
 	"ADDITIVE_OP",
@@ -75,7 +77,7 @@ const yyEofCode = 1
 const yyErrCode = 2
 const yyMaxDepth = 200
 
-//line parser.y:250
+//line parser.y:261
 
 //line yacctab:1
 var yyExca = []int{
@@ -87,91 +89,97 @@ var yyExca = []int{
 	-2, 10,
 }
 
-const yyNprod = 50
+const yyNprod = 52
 const yyPrivate = 57344
 
 var yyTokenNames []string
 var yyStates []string
 
-const yyLast = 137
+const yyLast = 142
 
 var yyAct = []int{
 
-	56, 72, 50, 53, 30, 24, 6, 20, 49, 59,
-	22, 10, 51, 18, 13, 12, 21, 19, 20, 11,
-	18, 85, 36, 37, 38, 21, 19, 20, 81, 82,
-	8, 18, 52, 7, 48, 66, 21, 19, 20, 87,
-	18, 10, 51, 31, 13, 12, 60, 55, 28, 11,
-	65, 18, 21, 19, 20, 57, 21, 19, 20, 29,
-	8, 74, 23, 7, 62, 40, 39, 18, 73, 77,
-	76, 18, 80, 75, 10, 19, 20, 13, 12, 79,
-	86, 78, 11, 64, 89, 63, 41, 40, 71, 18,
-	46, 25, 93, 8, 44, 27, 7, 83, 69, 96,
-	94, 91, 58, 43, 9, 17, 54, 31, 92, 35,
-	33, 45, 16, 13, 97, 95, 90, 61, 73, 88,
-	32, 68, 25, 34, 2, 3, 14, 5, 4, 1,
-	42, 84, 15, 26, 70, 67, 47,
+	58, 76, 55, 52, 51, 30, 45, 6, 24, 20,
+	61, 22, 10, 53, 18, 13, 12, 84, 68, 83,
+	67, 11, 18, 36, 37, 38, 21, 19, 20, 21,
+	19, 20, 8, 54, 90, 7, 50, 21, 19, 20,
+	92, 18, 10, 53, 18, 13, 12, 70, 63, 62,
+	88, 11, 18, 57, 31, 21, 19, 20, 86, 87,
+	66, 40, 8, 10, 78, 7, 13, 12, 79, 69,
+	18, 29, 11, 80, 82, 81, 28, 85, 21, 19,
+	20, 19, 20, 8, 91, 77, 7, 41, 40, 94,
+	25, 23, 39, 18, 59, 18, 44, 98, 27, 73,
+	101, 99, 60, 96, 17, 43, 75, 9, 46, 56,
+	31, 47, 16, 33, 97, 102, 13, 65, 35, 48,
+	100, 95, 64, 32, 77, 93, 72, 25, 34, 2,
+	3, 14, 5, 4, 1, 42, 89, 15, 26, 74,
+	71, 49,
 }
 var yyPact = []int{
 
-	120, -1000, -1000, 68, 94, -1000, 41, 68, 116, 70,
-	20, 31, -1000, -1000, -1000, 104, 117, -1000, 101, 68,
-	68, 68, 37, 60, -1000, 79, -1000, 85, 5, 68,
-	93, 19, 30, -1000, 83, -22, -10, -17, 59, -1000,
-	116, -1000, 110, -1000, -1000, -1000, 38, 56, -1000, -1000,
-	41, -1000, 21, 7, -1000, 115, 74, 62, 68, -1000,
-	-1000, -1000, -1000, -1000, 35, 95, 68, 52, -1000, 68,
-	2, -1000, -1000, 73, 1, -1000, 93, 10, -1000, 113,
-	41, -1000, 112, 109, 80, 100, -1000, -1000, -1000, -1000,
-	-1000, 30, -1000, 78, 108, 76, 107, -1000,
+	125, -1000, -1000, 57, 93, -1000, 21, 57, 121, 72,
+	47, 42, -1000, -1000, -1000, 107, 122, -1000, 110, 57,
+	57, 57, 62, 60, -1000, 80, 94, 84, 6, 57,
+	96, 24, 68, -1000, 82, -22, -9, -17, 64, -1000,
+	121, 94, 115, -1000, -1000, -1000, 109, -1000, 33, -10,
+	-1000, -1000, 21, -1000, 39, 18, -1000, 120, 74, 79,
+	57, 94, -1000, -1000, -1000, -1000, -1000, -1000, 36, 98,
+	57, -11, -1000, 57, 31, -1000, -1000, 25, 13, -1000,
+	-1000, 96, 10, -1000, 119, 21, -1000, 118, 114, 81,
+	106, -1000, -1000, -1000, -1000, -1000, 68, -1000, 78, 113,
+	76, 108, -1000,
 }
 var yyPgo = []int{
 
-	0, 136, 135, 4, 1, 134, 0, 5, 62, 133,
-	2, 8, 132, 3, 131, 104, 130, 129, 128, 127,
-	126,
+	0, 141, 140, 5, 1, 139, 0, 8, 91, 138,
+	3, 4, 137, 2, 136, 107, 135, 6, 134, 133,
+	132, 131,
 }
 var yyR1 = []int{
 
-	0, 17, 17, 18, 18, 19, 20, 20, 14, 14,
+	0, 18, 18, 19, 19, 20, 21, 21, 14, 14,
 	12, 12, 15, 15, 6, 6, 6, 5, 5, 4,
-	9, 9, 9, 8, 8, 7, 16, 16, 10, 10,
+	9, 9, 9, 8, 8, 7, 16, 16, 17, 17,
 	10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
-	13, 13, 3, 3, 2, 2, 1, 1, 11, 11,
+	10, 10, 13, 13, 3, 3, 2, 2, 1, 1,
+	11, 11,
 }
 var yyR2 = []int{
 
 	0, 2, 2, 0, 2, 1, 5, 11, 0, 2,
 	0, 1, 1, 1, 0, 3, 2, 1, 3, 3,
-	0, 2, 3, 1, 3, 3, 1, 1, 3, 3,
-	2, 4, 3, 4, 6, 6, 3, 3, 3, 1,
-	0, 1, 0, 4, 1, 3, 1, 3, 1, 1,
+	0, 2, 3, 1, 3, 3, 1, 1, 0, 2,
+	3, 4, 3, 4, 3, 5, 6, 6, 3, 3,
+	3, 1, 0, 1, 0, 4, 1, 3, 1, 3,
+	1, 1,
 }
 var yyChk = []int{
 
-	-1000, -17, 4, 5, -18, -19, -10, 28, 25, -15,
-	6, 14, 10, 9, -20, -12, 18, 11, 30, 16,
-	17, 15, -10, -8, -7, 6, -9, 25, 28, 28,
-	-3, 12, -15, 6, 6, 8, -10, -10, -10, 29,
-	27, 26, -16, 24, 15, 26, -8, -1, 29, -11,
-	-10, 7, -10, -13, 13, 28, -6, 25, 19, 31,
-	-7, 7, 26, 29, 27, 29, 28, -2, 6, 24,
-	-5, 26, -4, 6, -10, -11, -3, -10, 29, 27,
-	-10, 26, 27, 24, -14, 20, -13, 29, 6, -4,
-	7, 21, 8, -6, 22, 7, 23, 7,
+	-1000, -18, 4, 5, -19, -20, -10, 29, 26, -15,
+	6, 15, 10, 9, -21, -12, 19, 11, 31, 17,
+	18, 16, -10, -8, -7, 6, -9, 26, 29, 29,
+	-3, 12, -15, 6, 6, 8, -10, -10, -10, 30,
+	28, 27, -16, 25, 16, -17, 14, 27, -8, -1,
+	30, -11, -10, 7, -10, -13, 13, 29, -6, 26,
+	20, 32, -7, -17, 7, 8, 27, 30, 28, 30,
+	29, -2, 6, 25, -5, 27, -4, 6, -10, -17,
+	-11, -3, -10, 30, 28, -10, 27, 28, 25, -14,
+	21, -13, 30, 6, -4, 7, 22, 8, -6, 23,
+	7, 24, 7,
 }
 var yyDef = []int{
 
 	0, -2, 3, 0, -2, 2, 5, 0, 0, 20,
-	13, 42, 39, 12, 4, 0, 0, 11, 0, 0,
-	0, 0, 0, 0, 23, 0, 30, 0, 0, 0,
-	40, 0, 14, 13, 0, 0, 36, 37, 38, 28,
-	0, 29, 0, 26, 27, 21, 0, 0, 32, 46,
-	48, 49, 0, 0, 41, 0, 0, 0, 0, 33,
-	24, 25, 22, 31, 0, 42, 0, 0, 44, 0,
-	0, 16, 17, 0, 8, 47, 40, 0, 43, 0,
-	6, 15, 0, 0, 0, 0, 34, 35, 45, 18,
-	19, 14, 9, 0, 0, 0, 0, 7,
+	13, 44, 41, 12, 4, 0, 0, 11, 0, 0,
+	0, 0, 0, 0, 23, 0, 28, 0, 0, 0,
+	42, 0, 14, 13, 0, 0, 38, 39, 40, 30,
+	0, 28, 0, 26, 27, 32, 0, 21, 0, 0,
+	34, 48, 50, 51, 0, 0, 43, 0, 0, 0,
+	0, 28, 24, 31, 25, 29, 22, 33, 0, 44,
+	0, 0, 46, 0, 0, 16, 17, 0, 8, 35,
+	49, 42, 0, 45, 0, 6, 15, 0, 0, 0,
+	0, 36, 37, 47, 18, 19, 14, 9, 0, 0,
+	0, 0, 7,
 }
 var yyTok1 = []int{
 
@@ -179,21 +187,21 @@ var yyTok1 = []int{
 	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
 	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
 	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
-	28, 29, 3, 3, 27, 3, 3, 3, 3, 3,
+	29, 30, 3, 3, 28, 3, 3, 3, 3, 3,
 	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
-	3, 24, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 25, 3, 3, 3, 3, 3, 3, 3, 3,
 	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
 	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
-	3, 30, 3, 31, 3, 3, 3, 3, 3, 3,
+	3, 31, 3, 32, 3, 3, 3, 3, 3, 3,
 	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
 	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
-	3, 3, 3, 25, 3, 26,
+	3, 3, 3, 26, 3, 27,
 }
 var yyTok2 = []int{
 
 	2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
 	12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
-	22, 23,
+	22, 23, 24,
 }
 var yyTok3 = []int{
 	0,
@@ -559,27 +567,46 @@ yydefault:
 	case 28:
 		//line parser.y:156
 		{
-			yyVAL.ruleNode = yyS[yypt-1].ruleNode
+			yyVAL.str = "0s"
 		}
 	case 29:
 		//line parser.y:158
 		{
-			yyVAL.ruleNode = ast.NewVectorSelector(yyS[yypt-1].labelMatchers)
+			yyVAL.str = yyS[yypt-0].str
 		}
 	case 30:
-		//line parser.y:160
+		//line parser.y:162
+		{
+			yyVAL.ruleNode = yyS[yypt-1].ruleNode
+		}
+	case 31:
+		//line parser.y:164
 		{
 			var err error
-			m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue(yyS[yypt-1].str))
+			yyVAL.ruleNode, err = NewVectorSelector(yyS[yypt-2].labelMatchers, yyS[yypt-0].str)
 			if err != nil {
 				yylex.Error(err.Error())
 				return 1
 			}
-			yyS[yypt-0].labelMatchers = append(yyS[yypt-0].labelMatchers, m)
-			yyVAL.ruleNode = ast.NewVectorSelector(yyS[yypt-0].labelMatchers)
 		}
-	case 31:
-		//line parser.y:168
+	case 32:
+		//line parser.y:170
+		{
+			var err error
+			m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue(yyS[yypt-2].str))
+			if err != nil {
+				yylex.Error(err.Error())
+				return 1
+			}
+			yyS[yypt-1].labelMatchers = append(yyS[yypt-1].labelMatchers, m)
+			yyVAL.ruleNode, err = NewVectorSelector(yyS[yypt-1].labelMatchers, yyS[yypt-0].str)
+			if err != nil {
+				yylex.Error(err.Error())
+				return 1
+			}
+		}
+	case 33:
+		//line parser.y:179
 		{
 			var err error
 			yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-3].str, yyS[yypt-1].ruleNodeSlice)
@@ -588,8 +615,8 @@ yydefault:
 				return 1
 			}
 		}
-	case 32:
-		//line parser.y:174
+	case 34:
+		//line parser.y:185
 		{
 			var err error
 			yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-2].str, []ast.Node{})
@@ -598,18 +625,18 @@ yydefault:
 				return 1
 			}
 		}
-	case 33:
-		//line parser.y:180
+	case 35:
+		//line parser.y:191
 		{
 			var err error
-			yyVAL.ruleNode, err = NewMatrixSelector(yyS[yypt-3].ruleNode, yyS[yypt-1].str)
+			yyVAL.ruleNode, err = NewMatrixSelector(yyS[yypt-4].ruleNode, yyS[yypt-2].str, yyS[yypt-0].str)
 			if err != nil {
 				yylex.Error(err.Error())
 				return 1
 			}
 		}
-	case 34:
-		//line parser.y:186
+	case 36:
+		//line parser.y:197
 		{
 			var err error
 			yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-3].ruleNode, yyS[yypt-1].labelNameSlice, yyS[yypt-0].boolean)
@@ -618,8 +645,8 @@ yydefault:
 				return 1
 			}
 		}
-	case 35:
-		//line parser.y:192
+	case 37:
+		//line parser.y:203
 		{
 			var err error
 			yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-1].ruleNode, yyS[yypt-4].labelNameSlice, yyS[yypt-3].boolean)
@@ -628,28 +655,8 @@ yydefault:
 				return 1
 			}
 		}
-	case 36:
-		//line parser.y:200
-		{
-			var err error
-			yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
-			if err != nil {
-				yylex.Error(err.Error())
-				return 1
-			}
-		}
-	case 37:
-		//line parser.y:206
-		{
-			var err error
-			yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
-			if err != nil {
-				yylex.Error(err.Error())
-				return 1
-			}
-		}
 	case 38:
-		//line parser.y:212
+		//line parser.y:211
 		{
 			var err error
 			yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
@@ -659,57 +666,77 @@ yydefault:
 			}
 		}
 	case 39:
-		//line parser.y:218
+		//line parser.y:217
+		{
+			var err error
+			yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
+			if err != nil {
+				yylex.Error(err.Error())
+				return 1
+			}
+		}
+	case 40:
+		//line parser.y:223
+		{
+			var err error
+			yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
+			if err != nil {
+				yylex.Error(err.Error())
+				return 1
+			}
+		}
+	case 41:
+		//line parser.y:229
 		{
 			yyVAL.ruleNode = ast.NewScalarLiteral(yyS[yypt-0].num)
 		}
-	case 40:
-		//line parser.y:222
+	case 42:
+		//line parser.y:233
 		{
 			yyVAL.boolean = false
 		}
-	case 41:
-		//line parser.y:224
+	case 43:
+		//line parser.y:235
 		{
 			yyVAL.boolean = true
 		}
-	case 42:
-		//line parser.y:228
+	case 44:
+		//line parser.y:239
 		{
 			yyVAL.labelNameSlice = clientmodel.LabelNames{}
 		}
-	case 43:
-		//line parser.y:230
+	case 45:
+		//line parser.y:241
 		{
 			yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice
 		}
-	case 44:
-		//line parser.y:234
+	case 46:
+		//line parser.y:245
 		{
 			yyVAL.labelNameSlice = clientmodel.LabelNames{clientmodel.LabelName(yyS[yypt-0].str)}
 		}
-	case 45:
-		//line parser.y:236
+	case 47:
+		//line parser.y:247
 		{
 			yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, clientmodel.LabelName(yyS[yypt-0].str))
 		}
-	case 46:
-		//line parser.y:240
+	case 48:
+		//line parser.y:251
 		{
 			yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode}
 		}
-	case 47:
-		//line parser.y:242
+	case 49:
+		//line parser.y:253
 		{
 			yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode)
 		}
-	case 48:
-		//line parser.y:246
+	case 50:
+		//line parser.y:257
 		{
 			yyVAL.ruleNode = yyS[yypt-0].ruleNode
 		}
-	case 49:
-		//line parser.y:248
+	case 51:
+		//line parser.y:259
 		{
 			yyVAL.ruleNode = ast.NewStringLiteral(yyS[yypt-0].str)
 		}
diff --git a/rules/rules_test.go b/rules/rules_test.go
index 70ff6d8438..ca7394bc98 100644
--- a/rules/rules_test.go
+++ b/rules/rules_test.go
@@ -62,42 +62,32 @@ func newTestStorage(t testing.TB) (storage local.Storage, closer test.Closer) {
 func TestExpressions(t *testing.T) {
 	// Labels in expected output need to be alphabetically sorted.
 	expressionTests := []struct {
-		expr           string
-		output         []string
-		shouldFail     bool
-		checkOrder     bool
-		fullRanges     int
-		intervalRanges int
+		expr       string
+		output     []string
+		shouldFail bool
+		checkOrder bool
 	}{
 		{
-			expr:           `SUM(http_requests)`,
-			output:         []string{`{} => 3600 @[%v]`},
-			fullRanges:     0,
-			intervalRanges: 8,
+			expr:   `SUM(http_requests)`,
+			output: []string{`{} => 3600 @[%v]`},
 		}, {
 			expr: `SUM(http_requests{instance="0"}) BY(job)`,
 			output: []string{
 				`{job="api-server"} => 400 @[%v]`,
 				`{job="app-server"} => 1200 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 4,
 		}, {
 			expr: `SUM(http_requests{instance="0"}) BY(job) KEEPING_EXTRA`,
 			output: []string{
 				`{instance="0", job="api-server"} => 400 @[%v]`,
 				`{instance="0", job="app-server"} => 1200 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 4,
 		}, {
 			expr: `SUM(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => 1000 @[%v]`,
 				`{job="app-server"} => 2600 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			// Non-existent labels mentioned in BY-clauses shouldn't propagate to output.
 			expr: `SUM(http_requests) BY (job, nonexistent)`,
@@ -105,8 +95,6 @@ func TestExpressions(t *testing.T) {
 				`{job="api-server"} => 1000 @[%v]`,
 				`{job="app-server"} => 2600 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `
 				// Test comment.
@@ -116,16 +104,12 @@ func TestExpressions(t *testing.T) {
 				`{job="api-server"} => 1000 @[%v]`,
 				`{job="app-server"} => 2600 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `COUNT(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => 4 @[%v]`,
 				`{job="app-server"} => 4 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job, group)`,
 			output: []string{
@@ -134,139 +118,103 @@ func TestExpressions(t *testing.T) {
 				`{group="production", job="api-server"} => 300 @[%v]`,
 				`{group="production", job="app-server"} => 1100 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `AVG(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => 250 @[%v]`,
 				`{job="app-server"} => 650 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `MIN(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => 100 @[%v]`,
 				`{job="app-server"} => 500 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `MAX(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => 400 @[%v]`,
 				`{job="app-server"} => 800 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) - COUNT(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => 996 @[%v]`,
 				`{job="app-server"} => 2596 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `2 - SUM(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => -998 @[%v]`,
 				`{job="app-server"} => -2598 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `1000 / SUM(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => 1 @[%v]`,
 				`{job="app-server"} => 0.38461538461538464 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) - 2`,
 			output: []string{
 				`{job="api-server"} => 998 @[%v]`,
 				`{job="app-server"} => 2598 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) % 3`,
 			output: []string{
 				`{job="api-server"} => 1 @[%v]`,
 				`{job="app-server"} => 2 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) / 0`,
 			output: []string{
 				`{job="api-server"} => +Inf @[%v]`,
 				`{job="app-server"} => +Inf @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) > 1000`,
 			output: []string{
 				`{job="app-server"} => 2600 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `1000 < SUM(http_requests) BY (job)`,
 			output: []string{
 				`{job="app-server"} => 1000 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) <= 1000`,
 			output: []string{
 				`{job="api-server"} => 1000 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) != 1000`,
 			output: []string{
 				`{job="app-server"} => 2600 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) == 1000`,
 			output: []string{
 				`{job="api-server"} => 1000 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `SUM(http_requests) BY (job) + SUM(http_requests) BY (job)`,
 			output: []string{
 				`{job="api-server"} => 2000 @[%v]`,
 				`{job="app-server"} => 5200 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			expr: `http_requests{job="api-server", group="canary"}`,
 			output: []string{
 				`http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`,
 				`http_requests{group="canary", instance="1", job="api-server"} => 400 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		}, {
 			expr: `http_requests{job="api-server", group="canary"} + rate(http_requests{job="api-server"}[5m]) * 5 * 60`,
 			output: []string{
 				`{group="canary", instance="0", job="api-server"} => 330 @[%v]`,
 				`{group="canary", instance="1", job="api-server"} => 440 @[%v]`,
 			},
-			fullRanges:     4,
-			intervalRanges: 0,
 		}, {
 			expr: `rate(http_requests[25m]) * 25 * 60`,
 			output: []string{
@@ -279,8 +227,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="1", job="api-server"} => 100 @[%v]`,
 				`{group="production", instance="1", job="app-server"} => 300 @[%v]`,
 			},
-			fullRanges:     8,
-			intervalRanges: 0,
 		}, {
 			expr: `delta(http_requests[25m], 1)`,
 			output: []string{
@@ -293,8 +239,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="1", job="api-server"} => 100 @[%v]`,
 				`{group="production", instance="1", job="app-server"} => 300 @[%v]`,
 			},
-			fullRanges:     8,
-			intervalRanges: 0,
 		}, {
 			expr: `sort(http_requests)`,
 			output: []string{
@@ -307,9 +251,7 @@ func TestExpressions(t *testing.T) {
 				`http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`,
 				`http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`,
 			},
-			checkOrder:     true,
-			fullRanges:     0,
-			intervalRanges: 8,
+			checkOrder: true,
 		}, {
 			expr: `sort_desc(http_requests)`,
 			output: []string{
@@ -322,9 +264,7 @@ func TestExpressions(t *testing.T) {
 				`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`,
 				`http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`,
 			},
-			checkOrder:     true,
-			fullRanges:     0,
-			intervalRanges: 8,
+			checkOrder: true,
 		}, {
 			expr: `topk(3, http_requests)`,
 			output: []string{
@@ -332,18 +272,14 @@ func TestExpressions(t *testing.T) {
 				`http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`,
 				`http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`,
 			},
-			checkOrder:     true,
-			fullRanges:     0,
-			intervalRanges: 8,
+			checkOrder: true,
 		}, {
 			expr: `topk(5, http_requests{group="canary",job="app-server"})`,
 			output: []string{
 				`http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`,
 				`http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`,
 			},
-			checkOrder:     true,
-			fullRanges:     0,
-			intervalRanges: 2,
+			checkOrder: true,
 		}, {
 			expr: `bottomk(3, http_requests)`,
 			output: []string{
@@ -351,26 +287,20 @@ func TestExpressions(t *testing.T) {
 				`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`,
 				`http_requests{group="canary", instance="0", job="api-server"} => 300 @[%v]`,
 			},
-			checkOrder:     true,
-			fullRanges:     0,
-			intervalRanges: 8,
+			checkOrder: true,
 		}, {
 			expr: `bottomk(5, http_requests{group="canary",job="app-server"})`,
 			output: []string{
 				`http_requests{group="canary", instance="0", job="app-server"} => 700 @[%v]`,
 				`http_requests{group="canary", instance="1", job="app-server"} => 800 @[%v]`,
 			},
-			checkOrder:     true,
-			fullRanges:     0,
-			intervalRanges: 2,
+			checkOrder: true,
 		}, {
 			// Single-letter label names and values.
 			expr: `x{y="testvalue"}`,
 			output: []string{
 				`x{y="testvalue"} => 100 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 1,
 		}, {
 			// Lower-cased aggregation operators should work too.
 			expr: `sum(http_requests) by (job) + min(http_requests) by (job) + max(http_requests) by (job) + avg(http_requests) by (job)`,
@@ -378,62 +308,42 @@ func TestExpressions(t *testing.T) {
 				`{job="app-server"} => 4550 @[%v]`,
 				`{job="api-server"} => 1750 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		}, {
 			// Deltas should be adjusted for target interval vs. samples under target interval.
-			expr:           `delta(http_requests{group="canary", instance="1", job="app-server"}[18m])`,
-			output:         []string{`{group="canary", instance="1", job="app-server"} => 288 @[%v]`},
-			fullRanges:     1,
-			intervalRanges: 0,
+			expr:   `delta(http_requests{group="canary", instance="1", job="app-server"}[18m])`,
+			output: []string{`{group="canary", instance="1", job="app-server"} => 288 @[%v]`},
 		}, {
 			// Deltas should perform the same operation when 2nd argument is 0.
-			expr:           `delta(http_requests{group="canary", instance="1", job="app-server"}[18m], 0)`,
-			output:         []string{`{group="canary", instance="1", job="app-server"} => 288 @[%v]`},
-			fullRanges:     1,
-			intervalRanges: 0,
+			expr:   `delta(http_requests{group="canary", instance="1", job="app-server"}[18m], 0)`,
+			output: []string{`{group="canary", instance="1", job="app-server"} => 288 @[%v]`},
 		}, {
 			// Rates should calculate per-second rates.
-			expr:           `rate(http_requests{group="canary", instance="1", job="app-server"}[60m])`,
-			output:         []string{`{group="canary", instance="1", job="app-server"} => 0.26666666666666666 @[%v]`},
-			fullRanges:     1,
-			intervalRanges: 0,
+			expr:   `rate(http_requests{group="canary", instance="1", job="app-server"}[60m])`,
+			output: []string{`{group="canary", instance="1", job="app-server"} => 0.26666666666666666 @[%v]`},
 		}, {
 			// Deriv should return the same as rate in simple cases.
-			expr:           `deriv(http_requests{group="canary", instance="1", job="app-server"}[60m])`,
-			output:         []string{`{group="canary", instance="1", job="app-server"} => 0.26666666666666666 @[%v]`},
-			fullRanges:     1,
-			intervalRanges: 0,
+			expr:   `deriv(http_requests{group="canary", instance="1", job="app-server"}[60m])`,
+			output: []string{`{group="canary", instance="1", job="app-server"} => 0.26666666666666666 @[%v]`},
 		}, {
 			// Counter resets at in the middle of range are handled correctly by rate().
-			expr:           `rate(testcounter_reset_middle[60m])`,
-			output:         []string{`{} => 0.03 @[%v]`},
-			fullRanges:     1,
-			intervalRanges: 0,
+			expr:   `rate(testcounter_reset_middle[60m])`,
+			output: []string{`{} => 0.03 @[%v]`},
 		}, {
 			// Counter resets at end of range are ignored by rate().
-			expr:           `rate(testcounter_reset_end[5m])`,
-			output:         []string{`{} => 0 @[%v]`},
-			fullRanges:     1,
-			intervalRanges: 0,
+			expr:   `rate(testcounter_reset_end[5m])`,
+			output: []string{`{} => 0 @[%v]`},
 		}, {
 			// Deriv should return correct result.
-			expr:           `deriv(testcounter_reset_middle[100m])`,
-			output:         []string{`{} => 0.010606060606060607 @[%v]`},
-			fullRanges:     1,
-			intervalRanges: 0,
+			expr:   `deriv(testcounter_reset_middle[100m])`,
+			output: []string{`{} => 0.010606060606060607 @[%v]`},
 		}, {
 			// count_scalar for a non-empty vector should return scalar element count.
-			expr:           `count_scalar(http_requests)`,
-			output:         []string{`scalar: 8 @[%v]`},
-			fullRanges:     0,
-			intervalRanges: 8,
+			expr:   `count_scalar(http_requests)`,
+			output: []string{`scalar: 8 @[%v]`},
 		}, {
 			// count_scalar for an empty vector should return scalar 0.
-			expr:           `count_scalar(nonexistent)`,
-			output:         []string{`scalar: 0 @[%v]`},
-			fullRanges:     0,
-			intervalRanges: 0,
+			expr:   `count_scalar(nonexistent)`,
+			output: []string{`scalar: 0 @[%v]`},
 		}, {
 			// Empty expressions shouldn't parse.
 			expr:       ``,
@@ -454,8 +364,6 @@ func TestExpressions(t *testing.T) {
 				`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`,
 				`http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 4,
 		}, {
 			expr: `http_requests{job=~"server",group!="canary"}`,
 			output: []string{
@@ -464,29 +372,21 @@ func TestExpressions(t *testing.T) {
 				`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`,
 				`http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 4,
 		}, {
 			expr: `http_requests{job!~"api",group!="canary"}`,
 			output: []string{
 				`http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`,
 				`http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		}, {
-			expr:           `count_scalar(http_requests{job=~"^server$"})`,
-			output:         []string{`scalar: 0 @[%v]`},
-			fullRanges:     0,
-			intervalRanges: 0,
+			expr:   `count_scalar(http_requests{job=~"^server$"})`,
+			output: []string{`scalar: 0 @[%v]`},
 		}, {
 			expr: `http_requests{group="production",job=~"^api"}`,
 			output: []string{
 				`http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`,
 				`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `abs(-1 * http_requests{group="production",job="api-server"})`,
@@ -494,8 +394,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 100 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 200 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `floor(0.004 * http_requests{group="production",job="api-server"})`,
@@ -503,8 +401,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 0 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 0 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `ceil(0.004 * http_requests{group="production",job="api-server"})`,
@@ -512,8 +408,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 1 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `round(0.004 * http_requests{group="production",job="api-server"})`,
@@ -521,8 +415,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 0 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{ // Round should correctly handle negative numbers.
 			expr: `round(-1 * (0.004 * http_requests{group="production",job="api-server"}))`,
@@ -530,8 +422,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 0 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => -1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{ // Round should round half up.
 			expr: `round(0.005 * http_requests{group="production",job="api-server"})`,
@@ -539,8 +429,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 1 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `round(-1 * (0.005 * http_requests{group="production",job="api-server"}))`,
@@ -548,8 +436,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 0 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => -1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `round(1 + 0.005 * http_requests{group="production",job="api-server"})`,
@@ -557,8 +443,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 2 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 2 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `round(-1 * (1 + 0.005 * http_requests{group="production",job="api-server"}))`,
@@ -566,8 +450,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => -1 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => -2 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{ // Round should accept the number to round nearest to.
 			expr: `round(0.0005 * http_requests{group="production",job="api-server"}, 0.1)`,
@@ -575,8 +457,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 0.1 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 0.1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `round(2.1 + 0.0005 * http_requests{group="production",job="api-server"}, 0.1)`,
@@ -584,8 +464,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 2.2 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 2.2 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `round(5.2 + 0.0005 * http_requests{group="production",job="api-server"}, 0.1)`,
@@ -593,8 +471,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 5.3 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 5.3 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{ // Round should work correctly with negative numbers and multiple decimal places.
 			expr: `round(-1 * (5.2 + 0.0005 * http_requests{group="production",job="api-server"}), 0.1)`,
@@ -602,8 +478,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => -5.2 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => -5.3 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{ // Round should work correctly with big toNearests.
 			expr: `round(0.025 * http_requests{group="production",job="api-server"}, 5)`,
@@ -611,8 +485,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 5 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 5 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `round(0.045 * http_requests{group="production",job="api-server"}, 5)`,
@@ -620,8 +492,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 5 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 10 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `avg_over_time(http_requests{group="production",job="api-server"}[1h])`,
@@ -629,8 +499,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 50 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 100 @[%v]`,
 			},
-			fullRanges:     2,
-			intervalRanges: 0,
 		},
 		{
 			expr: `count_over_time(http_requests{group="production",job="api-server"}[1h])`,
@@ -638,8 +506,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 11 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 11 @[%v]`,
 			},
-			fullRanges:     2,
-			intervalRanges: 0,
 		},
 		{
 			expr: `max_over_time(http_requests{group="production",job="api-server"}[1h])`,
@@ -647,8 +513,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 100 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 200 @[%v]`,
 			},
-			fullRanges:     2,
-			intervalRanges: 0,
 		},
 		{
 			expr: `min_over_time(http_requests{group="production",job="api-server"}[1h])`,
@@ -656,8 +520,6 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 0 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 0 @[%v]`,
 			},
-			fullRanges:     2,
-			intervalRanges: 0,
 		},
 		{
 			expr: `sum_over_time(http_requests{group="production",job="api-server"}[1h])`,
@@ -665,14 +527,10 @@ func TestExpressions(t *testing.T) {
 				`{group="production", instance="0", job="api-server"} => 550 @[%v]`,
 				`{group="production", instance="1", job="api-server"} => 1100 @[%v]`,
 			},
-			fullRanges:     2,
-			intervalRanges: 0,
 		},
 		{
-			expr:           `time()`,
-			output:         []string{`scalar: 3000 @[%v]`},
-			fullRanges:     0,
-			intervalRanges: 0,
+			expr:   `time()`,
+			output: []string{`scalar: 3000 @[%v]`},
 		},
 		{
 			expr: `drop_common_labels(http_requests{group="production",job="api-server"})`,
@@ -680,8 +538,6 @@ func TestExpressions(t *testing.T) {
 				`http_requests{instance="0"} => 100 @[%v]`,
 				`http_requests{instance="1"} => 200 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 2,
 		},
 		{
 			expr: `{` + string(clientmodel.MetricNameLabel) + `=~".*"}`,
@@ -698,8 +554,6 @@ func TestExpressions(t *testing.T) {
 				`testcounter_reset_middle => 50 @[%v]`,
 				`x{y="testvalue"} => 100 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 11,
 		},
 		{
 			expr: `{job=~"server", job!~"api"}`,
@@ -709,8 +563,6 @@ func TestExpressions(t *testing.T) {
 				`http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`,
 				`http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 4,
 		},
 		{
 			// Test alternative "by"-clause order.
@@ -719,8 +571,6 @@ func TestExpressions(t *testing.T) {
 				`{group="canary"} => 700 @[%v]`,
 				`{group="production"} => 300 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 4,
 		},
 		{
 			// Test alternative "by"-clause order with "keeping_extra".
@@ -729,8 +579,6 @@ func TestExpressions(t *testing.T) {
 				`{group="canary", job="api-server"} => 700 @[%v]`,
 				`{group="production", job="api-server"} => 300 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 4,
 		},
 		{
 			// Test both alternative "by"-clause orders in one expression.
@@ -740,48 +588,50 @@ func TestExpressions(t *testing.T) {
 			output: []string{
 				`{job="api-server"} => 1000 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 4,
 		},
 		{
 			expr: `absent(nonexistent)`,
 			output: []string{
 				`{} => 1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 0,
 		},
 		{
 			expr: `absent(nonexistent{job="testjob", instance="testinstance", method=~".*"})`,
 			output: []string{
 				`{instance="testinstance", job="testjob"} => 1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 0,
 		},
 		{
 			expr: `count_scalar(absent(http_requests))`,
 			output: []string{
 				`scalar: 0 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		},
 		{
 			expr: `count_scalar(absent(sum(http_requests)))`,
 			output: []string{
 				`scalar: 0 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 8,
 		},
 		{
 			expr: `absent(sum(nonexistent{job="testjob", instance="testinstance"}))`,
 			output: []string{
 				`{} => 1 @[%v]`,
 			},
-			fullRanges:     0,
-			intervalRanges: 0,
+		},
+		{
+			expr: `http_requests{group="production",job="api-server"} offset 5m`,
+			output: []string{
+				`http_requests{group="production", instance="0", job="api-server"} => 90 @[%v]`,
+				`http_requests{group="production", instance="1", job="api-server"} => 180 @[%v]`,
+			},
+		},
+		{
+			expr: `rate(http_requests{group="production",job="api-server"}[10m] offset 5m)`,
+			output: []string{
+				`{group="production", instance="0", job="api-server"} => 0.03333333333333333 @[%v]`,
+				`{group="production", instance="1", job="api-server"} => 0.06666666666666667 @[%v]`,
+			},
 		},
 	}
 
@@ -834,17 +684,6 @@ func TestExpressions(t *testing.T) {
 				}
 			}
 
-			analyzer := ast.NewQueryAnalyzer(storage)
-			ast.Walk(analyzer, testExpr)
-			if exprTest.fullRanges != len(analyzer.FullRanges) {
-				t.Errorf("%d. Count of full ranges didn't match: %v vs %v", i, exprTest.fullRanges, len(analyzer.FullRanges))
-				failed = true
-			}
-			if exprTest.intervalRanges != len(analyzer.IntervalRanges) {
-				t.Errorf("%d. Count of interval ranges didn't match: %v vs %v", i, exprTest.intervalRanges, len(analyzer.IntervalRanges))
-				failed = true
-			}
-
 			if failed {
 				t.Errorf("%d. Expression: %v\n%v", i, exprTest.expr, vectorComparisonString(expectedLines, resultLines))
 			}