Expressions: Move GEL into core as expressions (#29072)
* comes from grafana/gel-app * remove transform plugin code * move __expr__ and -100 constants to expr pkg * set OrgID on request plugin context * use gtime for resample duration * in resample, rename "rule" to "window", use gtime for duration, parse duration before exec * remove gel entry from plugins-bundled/external.json which creates an empty array for plugins
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
}
|
||||
|
||||
func (i item) String() string {
|
||||
switch {
|
||||
case i.typ == itemEOF:
|
||||
return "EOF"
|
||||
case i.typ == itemError:
|
||||
return i.val
|
||||
case len(i.val) > 10:
|
||||
return fmt.Sprintf("%.10q...", i.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// itemType identifies the type of lex items.
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota // error occurred; value is text of error
|
||||
itemEOF
|
||||
itemNot // '!'
|
||||
itemAnd // '&&'
|
||||
itemOr // '||'
|
||||
itemGreater // '>'
|
||||
itemLess // '<'
|
||||
itemGreaterEq // '>='
|
||||
itemLessEq // '<='
|
||||
itemEq // '=='
|
||||
itemNotEq // '!='
|
||||
itemPlus // '+'
|
||||
itemMinus // '-'
|
||||
itemMult // '*'
|
||||
itemDiv // '/'
|
||||
itemMod // '%'
|
||||
itemNumber // simple number
|
||||
itemComma
|
||||
itemLeftParen
|
||||
itemRightParen
|
||||
itemString
|
||||
itemFunc
|
||||
itemVar // e.g. $A
|
||||
itemPow // '**'
|
||||
)
|
||||
|
||||
const eof = -1
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
input string // the string being scanned
|
||||
state stateFn // the next lexing function to enter
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
width Pos // width of last rune read from input
|
||||
lastPos Pos // position of most recent item returned by nextItem
|
||||
items chan item // channel of scanned items
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
// nolint:unused
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.ContainsRune(valid, l.next()) {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// lineNumber reports which line we're on, based on the position of
|
||||
// the previous item returned by nextItem. Doing it this way
|
||||
// means we don't have to worry about peek double counting.
|
||||
// nolint:unused
|
||||
func (l *lexer) lineNumber() int {
|
||||
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
func (l *lexer) nextItem() item {
|
||||
item := <-l.items
|
||||
l.lastPos = item.pos
|
||||
return item
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(input string) *lexer {
|
||||
l := &lexer{
|
||||
input: input,
|
||||
items: make(chan item),
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for l.state = lexItem; l.state != nil; {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
}
|
||||
|
||||
// state functions
|
||||
|
||||
func lexItem(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case r == '$':
|
||||
return lexVar
|
||||
case isSymbol(r):
|
||||
return lexSymbol
|
||||
case isNumber(r):
|
||||
l.backup()
|
||||
return lexNumber
|
||||
case unicode.IsLetter(r):
|
||||
return lexFunc
|
||||
case r == '(':
|
||||
l.emit(itemLeftParen)
|
||||
case r == ')':
|
||||
l.emit(itemRightParen)
|
||||
case r == '"':
|
||||
return lexString
|
||||
case r == ',':
|
||||
l.emit(itemComma)
|
||||
case isSpace(r):
|
||||
l.ignore()
|
||||
case r == eof:
|
||||
l.emit(itemEOF)
|
||||
break Loop
|
||||
default:
|
||||
return l.errorf("invalid character: %s", string(r))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||
// strconv) will notice.
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
if !l.scanNumber() {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(itemNumber)
|
||||
return lexItem
|
||||
}
|
||||
|
||||
func (l *lexer) scanNumber() bool {
|
||||
// Is it hex?
|
||||
digits := "0123456789"
|
||||
if l.accept("0") && l.accept("xX") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
}
|
||||
l.acceptRun(digits)
|
||||
if l.accept(".") {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun("0123456789")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const symbols = "!<>=&|+-*/%"
|
||||
|
||||
func lexSymbol(l *lexer) stateFn {
|
||||
l.acceptRun(symbols)
|
||||
s := l.input[l.start:l.pos]
|
||||
switch s {
|
||||
case "!":
|
||||
l.emit(itemNot)
|
||||
case "&&":
|
||||
l.emit(itemAnd)
|
||||
case "||":
|
||||
l.emit(itemOr)
|
||||
case ">":
|
||||
l.emit(itemGreater)
|
||||
case "<":
|
||||
l.emit(itemLess)
|
||||
case ">=":
|
||||
l.emit(itemGreaterEq)
|
||||
case "<=":
|
||||
l.emit(itemLessEq)
|
||||
case "==":
|
||||
l.emit(itemEq)
|
||||
case "!=":
|
||||
l.emit(itemNotEq)
|
||||
case "+":
|
||||
l.emit(itemPlus)
|
||||
case "-":
|
||||
l.emit(itemMinus)
|
||||
case "*":
|
||||
l.emit(itemMult)
|
||||
case "**":
|
||||
l.emit(itemPow)
|
||||
case "/":
|
||||
l.emit(itemDiv)
|
||||
case "%":
|
||||
l.emit(itemMod)
|
||||
default:
|
||||
l.emit(itemError)
|
||||
}
|
||||
return lexItem
|
||||
}
|
||||
|
||||
func lexFunc(l *lexer) stateFn {
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case unicode.IsLetter(r):
|
||||
// absorb
|
||||
default:
|
||||
l.backup()
|
||||
l.emit(itemFunc)
|
||||
return lexItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lexVar(l *lexer) stateFn {
|
||||
hasChar := false
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case unicode.IsLetter(r):
|
||||
hasChar = true
|
||||
// absorb
|
||||
default:
|
||||
if !hasChar {
|
||||
return l.errorf("incomplete variable")
|
||||
}
|
||||
l.backup()
|
||||
l.emit(itemVar)
|
||||
return lexItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lexString(l *lexer) stateFn {
|
||||
for {
|
||||
switch l.next() {
|
||||
case '"':
|
||||
l.emit(itemString)
|
||||
return lexItem
|
||||
case eof:
|
||||
return l.errorf("unterminated string")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
func isSpace(r rune) bool {
|
||||
return unicode.IsSpace(r)
|
||||
}
|
||||
|
||||
// isVarchar should maybe be used in place of unicode is letter above,
|
||||
// but do not want to modify it at this time, so adding lint exception.
|
||||
// nolint:unused,deadcode
|
||||
func isVarchar(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
||||
|
||||
func isSymbol(r rune) bool {
|
||||
return strings.ContainsRune(symbols, r)
|
||||
}
|
||||
|
||||
func isNumber(r rune) bool {
|
||||
return unicode.IsDigit(r) || r == '.'
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Make the types prettyprint.
|
||||
var itemName = map[itemType]string{
|
||||
itemError: "error",
|
||||
itemEOF: "EOF",
|
||||
itemNot: "!",
|
||||
itemAnd: "&&",
|
||||
itemOr: "||",
|
||||
itemGreater: ">",
|
||||
itemLess: "<",
|
||||
itemGreaterEq: ">=",
|
||||
itemLessEq: "<=",
|
||||
itemEq: "==",
|
||||
itemNotEq: "!=",
|
||||
itemPlus: "+",
|
||||
itemMinus: "-",
|
||||
itemMult: "*",
|
||||
itemDiv: "/",
|
||||
itemMod: "%",
|
||||
itemNumber: "number",
|
||||
itemComma: ",",
|
||||
itemLeftParen: "(",
|
||||
itemRightParen: ")",
|
||||
itemString: "string",
|
||||
itemFunc: "func",
|
||||
}
|
||||
|
||||
func (i itemType) String() string {
|
||||
s := itemName[i]
|
||||
if s == "" {
|
||||
return fmt.Sprintf("item%d", int(i))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type lexTest struct {
|
||||
name string
|
||||
input string
|
||||
items []item
|
||||
}
|
||||
|
||||
var (
|
||||
tEOF = item{itemEOF, 0, ""}
|
||||
tLt = item{itemLess, 0, "<"}
|
||||
tGt = item{itemGreater, 0, ">"}
|
||||
tOr = item{itemOr, 0, "||"}
|
||||
tNot = item{itemNot, 0, "!"}
|
||||
tAnd = item{itemAnd, 0, "&&"}
|
||||
tLtEq = item{itemLessEq, 0, "<="}
|
||||
tGtEq = item{itemGreaterEq, 0, ">="}
|
||||
tNotEq = item{itemNotEq, 0, "!="}
|
||||
tEq = item{itemEq, 0, "=="}
|
||||
tPlus = item{itemPlus, 0, "+"}
|
||||
tMinus = item{itemMinus, 0, "-"}
|
||||
tMult = item{itemMult, 0, "*"}
|
||||
tDiv = item{itemDiv, 0, "/"}
|
||||
tMod = item{itemMod, 0, "%"}
|
||||
)
|
||||
|
||||
var lexTests = []lexTest{
|
||||
{"empty", "", []item{tEOF}},
|
||||
{"spaces", " \t\n", []item{tEOF}},
|
||||
{"text", `"now is the time"`, []item{{itemString, 0, `"now is the time"`}, tEOF}},
|
||||
{"operators", "! && || < > <= >= == != + - * / %", []item{
|
||||
tNot,
|
||||
tAnd,
|
||||
tOr,
|
||||
tLt,
|
||||
tGt,
|
||||
tLtEq,
|
||||
tGtEq,
|
||||
tEq,
|
||||
tNotEq,
|
||||
tPlus,
|
||||
tMinus,
|
||||
tMult,
|
||||
tDiv,
|
||||
tMod,
|
||||
tEOF,
|
||||
}},
|
||||
{"numbers", "1 02 0x14 7.2 1e3 1.2e-4", []item{
|
||||
{itemNumber, 0, "1"},
|
||||
{itemNumber, 0, "02"},
|
||||
{itemNumber, 0, "0x14"},
|
||||
{itemNumber, 0, "7.2"},
|
||||
{itemNumber, 0, "1e3"},
|
||||
{itemNumber, 0, "1.2e-4"},
|
||||
tEOF,
|
||||
}},
|
||||
{"number plus var", "1 + $A", []item{
|
||||
{itemNumber, 0, "1"},
|
||||
tPlus,
|
||||
{itemVar, 0, "$A"},
|
||||
tEOF,
|
||||
}},
|
||||
// errors
|
||||
{"unclosed quote", "\"", []item{
|
||||
{itemError, 0, "unterminated string"},
|
||||
}},
|
||||
{"single quote", "'single quote is invalid'", []item{
|
||||
{itemError, 0, "invalid character: '"},
|
||||
}},
|
||||
{"invalid var", "$", []item{
|
||||
{itemError, 0, "incomplete variable"},
|
||||
}},
|
||||
}
|
||||
|
||||
// collect gathers the emitted items into a slice.
|
||||
func collect(t *lexTest) (items []item) {
|
||||
l := lex(t.input)
|
||||
for {
|
||||
item := l.nextItem()
|
||||
items = append(items, item)
|
||||
if item.typ == itemEOF || item.typ == itemError {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func equal(i1, i2 []item, checkPos bool) bool {
|
||||
if len(i1) != len(i2) {
|
||||
return false
|
||||
}
|
||||
for k := range i1 {
|
||||
if i1[k].typ != i2[k].typ {
|
||||
return false
|
||||
}
|
||||
if i1[k].val != i2[k].val {
|
||||
return false
|
||||
}
|
||||
if checkPos && i1[k].pos != i2[k].pos {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestLex(t *testing.T) {
|
||||
for i, test := range lexTests {
|
||||
items := collect(&lexTests[i])
|
||||
if !equal(items, test.items, false) {
|
||||
t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Parse nodes.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Node is an element in the parse tree. The interface is trivial.
|
||||
// The interface contains an unexported method so that only
|
||||
// types local to this package can satisfy it.
|
||||
type Node interface {
|
||||
Type() NodeType
|
||||
String() string
|
||||
StringAST() string
|
||||
Position() Pos // byte position of start of node in full original input string
|
||||
Check(*Tree) error // performs type checking for itself and sub-nodes
|
||||
Return() ReturnType
|
||||
|
||||
// Make sure only functions in this package can create Nodes.
|
||||
unexported()
|
||||
}
|
||||
|
||||
// NodeType identifies the type of a parse tree node.
|
||||
type NodeType int
|
||||
|
||||
// Pos represents a byte position in the original input text from which
|
||||
// this template was parsed.
|
||||
type Pos int
|
||||
|
||||
// Position returns the integer Position of p
|
||||
func (p Pos) Position() Pos {
|
||||
return p
|
||||
}
|
||||
|
||||
// unexported keeps Node implementations local to the package.
|
||||
// All implementations embed Pos, so this takes care of it.
|
||||
func (Pos) unexported() {
|
||||
}
|
||||
|
||||
// Type returns itself and provides an easy default implementation
|
||||
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
// NodeFunc is a function call.
|
||||
NodeFunc NodeType = iota
|
||||
// NodeBinary is a binary operator: math, logical, compare
|
||||
NodeBinary
|
||||
// NodeUnary is unary operator: !, -
|
||||
NodeUnary
|
||||
// NodeString is string constant.
|
||||
NodeString
|
||||
// NodeNumber is a numerical constant (Scalar).
|
||||
NodeNumber
|
||||
// NodeVar is variable: $A
|
||||
NodeVar
|
||||
)
|
||||
|
||||
// String returns the string representation of the NodeType
|
||||
func (t NodeType) String() string {
|
||||
switch t {
|
||||
case NodeFunc:
|
||||
return "NodeFunc"
|
||||
case NodeBinary:
|
||||
return "NodeBinary"
|
||||
case NodeUnary:
|
||||
return "NodeUnary"
|
||||
case NodeString:
|
||||
return "NodeString"
|
||||
case NodeNumber:
|
||||
return "NodeNumber"
|
||||
default:
|
||||
return "NodeUnknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes.
|
||||
|
||||
// VarNode holds a variable reference.
|
||||
type VarNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
Name string // Without the $
|
||||
Text string // Raw
|
||||
}
|
||||
|
||||
func newVar(pos Pos, name, text string) *VarNode {
|
||||
return &VarNode{NodeType: NodeVar, Pos: pos, Name: name, Text: text}
|
||||
}
|
||||
|
||||
// Type returns the Type of the VarNode so it fulfills the Node interface.
|
||||
func (n *VarNode) Type() NodeType { return NodeVar }
|
||||
|
||||
// String returns the string representation of the VarNode so it fulfills the Node interface.
|
||||
func (n *VarNode) String() string { return n.Text }
|
||||
|
||||
// StringAST returns the string representation of abstract syntax tree of the VarNode so it fulfills the Node interface.
|
||||
func (n *VarNode) StringAST() string { return n.String() }
|
||||
|
||||
// Check performs parse time checking on the VarNode so it fulfills the Node interface.
|
||||
func (n *VarNode) Check(*Tree) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return returns the result type of the VarNode so it fulfills the Node interface.
|
||||
func (n *VarNode) Return() ReturnType {
|
||||
return TypeSeriesSet // Vars are only time series for now I guess....
|
||||
}
|
||||
|
||||
// FuncNode holds a function invocation.
|
||||
type FuncNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
Name string
|
||||
F *Func
|
||||
Args []Node
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func newFunc(pos Pos, name string, f Func) *FuncNode {
|
||||
return &FuncNode{NodeType: NodeFunc, Pos: pos, Name: name, F: &f}
|
||||
}
|
||||
|
||||
func (f *FuncNode) append(arg Node) {
|
||||
f.Args = append(f.Args, arg)
|
||||
}
|
||||
|
||||
// String returns the string representation of the FuncNode so it fulfills the Node interface.
|
||||
func (f *FuncNode) String() string {
|
||||
s := f.Name + "("
|
||||
for i, arg := range f.Args {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += arg.String()
|
||||
}
|
||||
s += ")"
|
||||
return s
|
||||
}
|
||||
|
||||
// StringAST returns the string representation of abstract syntax tree of the FuncNode so it fulfills the Node interface.
|
||||
func (f *FuncNode) StringAST() string {
|
||||
s := f.Name + "("
|
||||
for i, arg := range f.Args {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += arg.StringAST()
|
||||
}
|
||||
s += ")"
|
||||
return s
|
||||
}
|
||||
|
||||
// Check performs parse time checking on the FuncNode so it fulfills the Node interface.
|
||||
func (f *FuncNode) Check(t *Tree) error {
|
||||
if len(f.Args) < len(f.F.Args) {
|
||||
return fmt.Errorf("parse: not enough arguments for %s", f.Name)
|
||||
} else if len(f.Args) > len(f.F.Args) {
|
||||
return fmt.Errorf("parse: too many arguments for %s", f.Name)
|
||||
}
|
||||
|
||||
for i, arg := range f.Args {
|
||||
funcType := f.F.Args[i]
|
||||
argType := arg.Return()
|
||||
// if funcType == TypeNumberSet && argType == TypeScalar {
|
||||
// argType = TypeNumberSet
|
||||
// }
|
||||
if funcType == TypeVariantSet {
|
||||
if !(argType == TypeNumberSet || argType == TypeSeriesSet || argType == TypeScalar) {
|
||||
return fmt.Errorf("parse: expected %v or %v for argument %v, got %v", TypeNumberSet, TypeSeriesSet, i, argType)
|
||||
}
|
||||
} else if funcType != argType {
|
||||
return fmt.Errorf("parse: expected %v, got %v for argument %v (%v)", funcType, argType, i, arg.String())
|
||||
}
|
||||
if err := arg.Check(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if f.F.Check != nil {
|
||||
return f.F.Check(t, f)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return returns the result type of the FuncNode so it fulfills the Node interface.
|
||||
func (f *FuncNode) Return() ReturnType {
|
||||
return f.F.Return
|
||||
}
|
||||
|
||||
// ScalarNode holds a number: signed or unsigned integer or float.
|
||||
// The value is parsed and stored under all the types that can represent the value.
|
||||
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
||||
type ScalarNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
IsUint bool // Number has an unsigned integral value.
|
||||
IsFloat bool // Number has a floating-point value.
|
||||
Uint64 uint64 // The unsigned integer value.
|
||||
Float64 float64 // The floating-point value.
|
||||
Text string // The original textual representation from the input.
|
||||
}
|
||||
|
||||
func newNumber(pos Pos, text string) (*ScalarNode, error) {
|
||||
n := &ScalarNode{NodeType: NodeNumber, Pos: pos, Text: text}
|
||||
// Do integer test first so we get 0x123 etc.
|
||||
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0.
|
||||
if err == nil {
|
||||
n.IsUint = true
|
||||
n.Uint64 = u
|
||||
}
|
||||
// If an integer extraction succeeded, promote the float.
|
||||
if n.IsUint {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Uint64)
|
||||
} else {
|
||||
f, err := strconv.ParseFloat(text, 64)
|
||||
if err == nil {
|
||||
n.IsFloat = true
|
||||
n.Float64 = f
|
||||
// If a floating-point extraction succeeded, extract the int if needed.
|
||||
if !n.IsUint && float64(uint64(f)) == f {
|
||||
n.IsUint = true
|
||||
n.Uint64 = uint64(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !n.IsUint && !n.IsFloat {
|
||||
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// String returns the string representation of the ScalarNode so it fulfills the Node interface.
|
||||
func (n *ScalarNode) String() string {
|
||||
return n.Text
|
||||
}
|
||||
|
||||
// StringAST returns the string representation of abstract syntax tree of the ScalarNode so it fulfills the Node interface.
|
||||
func (n *ScalarNode) StringAST() string {
|
||||
return n.String()
|
||||
}
|
||||
|
||||
// Check performs parse time checking on the ScalarNode so it fulfills the Node interface.
|
||||
func (n *ScalarNode) Check(*Tree) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return returns the result type of the ScalarNode so it fulfills the Node interface.
|
||||
func (n *ScalarNode) Return() ReturnType {
|
||||
return TypeScalar
|
||||
}
|
||||
|
||||
// StringNode holds a string constant. The value has been "unquoted".
|
||||
type StringNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
Quoted string // The original text of the string, with quotes.
|
||||
Text string // The string, after quote processing.
|
||||
}
|
||||
|
||||
func newString(pos Pos, orig, text string) *StringNode {
|
||||
return &StringNode{NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
||||
}
|
||||
|
||||
// String returns the string representation of the StringNode so it fulfills the Node interface.
|
||||
func (s *StringNode) String() string {
|
||||
return s.Quoted
|
||||
}
|
||||
|
||||
// StringAST returns the string representation of abstract syntax tree of the StringNode so it fulfills the Node interface.
|
||||
func (s *StringNode) StringAST() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// Check performs parse time checking on the StringNode so it fulfills the Node interface.
|
||||
func (s *StringNode) Check(*Tree) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return returns the result type of the TypeString so it fulfills the Node interface.
|
||||
func (s *StringNode) Return() ReturnType {
|
||||
return TypeString
|
||||
}
|
||||
|
||||
// BinaryNode holds two arguments and an operator.
|
||||
type BinaryNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
Args [2]Node
|
||||
Operator item
|
||||
OpStr string
|
||||
}
|
||||
|
||||
func newBinary(operator item, arg1, arg2 Node) *BinaryNode {
|
||||
return &BinaryNode{NodeType: NodeBinary, Pos: operator.pos, Args: [2]Node{arg1, arg2}, Operator: operator, OpStr: operator.val}
|
||||
}
|
||||
|
||||
// String returns the string representation of the BinaryNode so it fulfills the Node interface.
|
||||
func (b *BinaryNode) String() string {
|
||||
return fmt.Sprintf("%s %s %s", b.Args[0], b.Operator.val, b.Args[1])
|
||||
}
|
||||
|
||||
// StringAST returns the string representation of abstract syntax tree of the BinaryNode so it fulfills the Node interface.
|
||||
func (b *BinaryNode) StringAST() string {
|
||||
return fmt.Sprintf("%s(%s, %s)", b.Operator.val, b.Args[0], b.Args[1])
|
||||
}
|
||||
|
||||
// Check performs parse time checking on the BinaryNode so it fulfills the Node interface.
|
||||
func (b *BinaryNode) Check(t *Tree) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return returns the result type of the BinaryNode so it fulfills the Node interface.
|
||||
func (b *BinaryNode) Return() ReturnType {
|
||||
t0 := b.Args[0].Return()
|
||||
t1 := b.Args[1].Return()
|
||||
if t1 > t0 {
|
||||
return t1
|
||||
}
|
||||
return t0
|
||||
}
|
||||
|
||||
// UnaryNode holds one argument and an operator.
|
||||
type UnaryNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
Arg Node
|
||||
Operator item
|
||||
OpStr string
|
||||
}
|
||||
|
||||
func newUnary(operator item, arg Node) *UnaryNode {
|
||||
return &UnaryNode{NodeType: NodeUnary, Pos: operator.pos, Arg: arg, Operator: operator, OpStr: operator.val}
|
||||
}
|
||||
|
||||
// String returns the string representation of the UnaryNode so it fulfills the Node interface.
|
||||
func (u *UnaryNode) String() string {
|
||||
return fmt.Sprintf("%s%s", u.Operator.val, u.Arg)
|
||||
}
|
||||
|
||||
// StringAST returns the string representation of abstract syntax tree of the UnaryNode so it fulfills the Node interface.
|
||||
func (u *UnaryNode) StringAST() string {
|
||||
return fmt.Sprintf("%s(%s)", u.Operator.val, u.Arg)
|
||||
}
|
||||
|
||||
// Check performs parse time checking on the UnaryNode so it fulfills the Node interface.
|
||||
func (u *UnaryNode) Check(t *Tree) error {
|
||||
switch rt := u.Arg.Return(); rt {
|
||||
case TypeNumberSet, TypeSeriesSet, TypeScalar:
|
||||
return u.Arg.Check(t)
|
||||
default:
|
||||
return fmt.Errorf(`parse: type error in %s, expected "number", got %s`, u, rt)
|
||||
}
|
||||
}
|
||||
|
||||
// Return returns the result type of the UnaryNode so it fulfills the Node interface.
|
||||
func (u *UnaryNode) Return() ReturnType {
|
||||
return u.Arg.Return()
|
||||
}
|
||||
|
||||
// Walk invokes f on n and sub-nodes of n.
|
||||
func Walk(n Node, f func(Node)) {
|
||||
f(n)
|
||||
switch n := n.(type) {
|
||||
case *BinaryNode:
|
||||
Walk(n.Args[0], f)
|
||||
Walk(n.Args[1], f)
|
||||
case *FuncNode:
|
||||
for _, a := range n.Args {
|
||||
Walk(a, f)
|
||||
}
|
||||
case *ScalarNode, *StringNode:
|
||||
// Ignore since these node types have no sub nodes.
|
||||
case *UnaryNode:
|
||||
Walk(n.Arg, f)
|
||||
default:
|
||||
panic(fmt.Errorf("other type: %T", n))
|
||||
}
|
||||
}
|
||||
|
||||
// ReturnType represents the type that is returned from a node.
|
||||
type ReturnType int
|
||||
|
||||
const (
|
||||
// TypeString is a single string.
|
||||
TypeString ReturnType = iota
|
||||
// TypeScalar is a unlabled number constant.
|
||||
TypeScalar
|
||||
// TypeNumberSet is a collection of labelled numbers.
|
||||
TypeNumberSet
|
||||
// TypeSeriesSet is a collection of labelled time series.
|
||||
TypeSeriesSet
|
||||
// TypeVariantSet is a collection of the same type Number, Series, or Scalar.
|
||||
TypeVariantSet
|
||||
)
|
||||
|
||||
// String returns a string representation of the ReturnType.
|
||||
func (f ReturnType) String() string {
|
||||
switch f {
|
||||
case TypeNumberSet:
|
||||
return "numberSet"
|
||||
case TypeString:
|
||||
return "string"
|
||||
case TypeSeriesSet:
|
||||
return "seriesSet"
|
||||
case TypeScalar:
|
||||
return "scalar"
|
||||
case TypeVariantSet:
|
||||
return "variant"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package parse builds parse trees for expressions as defined by expr. Clients
|
||||
// should use that package to construct expressions rather than this one, which
|
||||
// provides shared internal data structures not intended for general use.
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tree is the representation of a single parsed expression.
|
||||
type Tree struct {
|
||||
Text string // text parsed to create the expression.
|
||||
Root Node // top-level root of the tree, returns a number.
|
||||
VarNames []string
|
||||
|
||||
funcs []map[string]Func
|
||||
|
||||
// Parsing only; cleared after parse.
|
||||
lex *lexer
|
||||
token [1]item // one-token lookahead for parser.
|
||||
peekCount int
|
||||
}
|
||||
|
||||
// Func holds the structure of a parsed function call.
|
||||
type Func struct {
|
||||
Args []ReturnType
|
||||
Return ReturnType
|
||||
F interface{}
|
||||
VariantReturn bool
|
||||
Check func(*Tree, *FuncNode) error
|
||||
}
|
||||
|
||||
// Parse returns a Tree, created by parsing the expression described in the
|
||||
// argument string. If an error is encountered, parsing stops and an empty Tree
|
||||
// is returned with the error.
|
||||
func Parse(text string, funcs ...map[string]Func) (t *Tree, err error) {
|
||||
t = New()
|
||||
t.Text = text
|
||||
err = t.Parse(text, funcs...)
|
||||
return
|
||||
}
|
||||
|
||||
// next returns the next token.
|
||||
func (t *Tree) next() item {
|
||||
if t.peekCount > 0 {
|
||||
t.peekCount--
|
||||
} else {
|
||||
t.token[0] = t.lex.nextItem()
|
||||
}
|
||||
return t.token[t.peekCount]
|
||||
}
|
||||
|
||||
// backup backs the input stream up one token.
|
||||
func (t *Tree) backup() {
|
||||
t.peekCount++
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next token.
|
||||
func (t *Tree) peek() item {
|
||||
if t.peekCount > 0 {
|
||||
return t.token[t.peekCount-1]
|
||||
}
|
||||
t.peekCount = 1
|
||||
t.token[0] = t.lex.nextItem()
|
||||
return t.token[0]
|
||||
}
|
||||
|
||||
// Parsing.
|
||||
|
||||
// New allocates a new parse tree with the given name.
|
||||
func New(funcs ...map[string]Func) *Tree {
|
||||
return &Tree{
|
||||
funcs: funcs,
|
||||
}
|
||||
}
|
||||
|
||||
// errorf formats the error and terminates processing.
|
||||
func (t *Tree) errorf(format string, args ...interface{}) {
|
||||
t.Root = nil
|
||||
format = fmt.Sprintf("expr: %s", format)
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// error terminates processing.
|
||||
func (t *Tree) error(err error) {
|
||||
t.errorf("%s", err)
|
||||
}
|
||||
|
||||
// expect consumes the next token and guarantees it has the required type.
|
||||
func (t *Tree) expect(expected itemType, context string) item {
|
||||
token := t.next()
|
||||
if token.typ != expected {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||
// nolint:unused
|
||||
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||
token := t.next()
|
||||
if token.typ != expected1 && token.typ != expected2 {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// unexpected complains about the token and terminates processing.
|
||||
func (t *Tree) unexpected(token item, context string) {
|
||||
t.errorf("unexpected %s in %s", token, context)
|
||||
}
|
||||
|
||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||
func (t *Tree) recover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
if _, ok := e.(runtime.Error); ok {
|
||||
panic(e)
|
||||
}
|
||||
if t != nil {
|
||||
t.stopParse()
|
||||
}
|
||||
*errp = e.(error)
|
||||
}
|
||||
}
|
||||
|
||||
// startParse initializes the parser, using the lexer.
|
||||
func (t *Tree) startParse(funcs []map[string]Func, lex *lexer) {
|
||||
t.Root = nil
|
||||
t.lex = lex
|
||||
t.funcs = funcs
|
||||
}
|
||||
|
||||
// stopParse terminates parsing.
|
||||
func (t *Tree) stopParse() {
|
||||
t.lex = nil
|
||||
}
|
||||
|
||||
// Parse parses the expression definition string to construct a representation
|
||||
// of the expression for execution.
|
||||
func (t *Tree) Parse(text string, funcs ...map[string]Func) (err error) {
|
||||
defer t.recover(&err)
|
||||
t.startParse(funcs, lex(text))
|
||||
t.Text = text
|
||||
t.parse()
|
||||
t.stopParse()
|
||||
return nil
|
||||
}
|
||||
|
||||
// parse is the top-level parser for an expression.
|
||||
// It runs to EOF.
|
||||
func (t *Tree) parse() {
|
||||
t.Root = t.O()
|
||||
t.expect(itemEOF, "root input")
|
||||
if err := t.Root.Check(t); err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
/* Grammar:
|
||||
O -> A {"||" A}
|
||||
A -> C {"&&" C}
|
||||
C -> P {( "==" | "!=" | ">" | ">=" | "<" | "<=") P}
|
||||
P -> M {( "+" | "-" ) M}
|
||||
M -> E {( "*" | "/" ) F}
|
||||
E -> F {( "**" ) F}
|
||||
F -> v | "(" O ")" | "!" O | "-" O
|
||||
v -> number | func(..) | queryVar
|
||||
Func -> name "(" param {"," param} ")"
|
||||
param -> number | "string" | queryVar
|
||||
*/
|
||||
|
||||
// expr:
|
||||
|
||||
// O is A {"||" A} in the grammar.
|
||||
func (t *Tree) O() Node {
|
||||
n := t.A()
|
||||
for {
|
||||
switch t.peek().typ {
|
||||
case itemOr:
|
||||
n = newBinary(t.next(), n, t.A())
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A is C {"&&" C} in the grammar.
|
||||
func (t *Tree) A() Node {
|
||||
n := t.C()
|
||||
for {
|
||||
switch t.peek().typ {
|
||||
case itemAnd:
|
||||
n = newBinary(t.next(), n, t.C())
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// C is C -> P {( "==" | "!=" | ">" | ">=" | "<" | "<=") P} in the grammar.
|
||||
func (t *Tree) C() Node {
|
||||
n := t.P()
|
||||
for {
|
||||
switch t.peek().typ {
|
||||
case itemEq, itemNotEq, itemGreater, itemGreaterEq, itemLess, itemLessEq:
|
||||
n = newBinary(t.next(), n, t.P())
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// P is M {( "+" | "-" ) M} in the grammar.
|
||||
func (t *Tree) P() Node {
|
||||
n := t.M()
|
||||
for {
|
||||
switch t.peek().typ {
|
||||
case itemPlus, itemMinus:
|
||||
n = newBinary(t.next(), n, t.M())
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// M is E {( "*" | "/" ) F} in the grammar.
|
||||
func (t *Tree) M() Node {
|
||||
n := t.E()
|
||||
for {
|
||||
switch t.peek().typ {
|
||||
case itemMult, itemDiv, itemMod:
|
||||
n = newBinary(t.next(), n, t.E())
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// E is F {( "**" ) F} in the grammar.
|
||||
func (t *Tree) E() Node {
|
||||
n := t.F()
|
||||
for {
|
||||
switch t.peek().typ {
|
||||
case itemPow:
|
||||
n = newBinary(t.next(), n, t.F())
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// F is v | "(" O ")" | "!" O | "-" O in the grammar.
|
||||
func (t *Tree) F() Node {
|
||||
switch token := t.peek(); token.typ {
|
||||
case itemNumber, itemFunc, itemVar:
|
||||
return t.v()
|
||||
case itemNot, itemMinus:
|
||||
return newUnary(t.next(), t.F())
|
||||
case itemLeftParen:
|
||||
t.next()
|
||||
n := t.O()
|
||||
t.expect(itemRightParen, "input: F()")
|
||||
return n
|
||||
default:
|
||||
t.unexpected(token, "input: F()")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// V is number | func(..) | queryVar in the grammar.
|
||||
func (t *Tree) v() Node {
|
||||
switch token := t.next(); token.typ {
|
||||
case itemNumber:
|
||||
n, err := newNumber(token.pos, token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return n
|
||||
case itemFunc:
|
||||
t.backup()
|
||||
return t.Func()
|
||||
case itemVar:
|
||||
t.backup()
|
||||
return t.Var()
|
||||
default:
|
||||
t.unexpected(token, "input: v()")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Var is queryVar in the grammar.
|
||||
func (t *Tree) Var() (v *VarNode) {
|
||||
token := t.next()
|
||||
varNoPrefix := strings.TrimPrefix(token.val, "$")
|
||||
t.VarNames = append(t.VarNames, varNoPrefix)
|
||||
return newVar(token.pos, varNoPrefix, token.val)
|
||||
}
|
||||
|
||||
// Func parses a FuncNode.
|
||||
func (t *Tree) Func() (f *FuncNode) {
|
||||
token := t.next()
|
||||
funcv, ok := t.GetFunction(token.val)
|
||||
if !ok {
|
||||
t.errorf("non existent function %s", token.val)
|
||||
}
|
||||
f = newFunc(token.pos, token.val, funcv)
|
||||
t.expect(itemLeftParen, "func")
|
||||
for {
|
||||
switch token = t.next(); token.typ {
|
||||
default:
|
||||
t.backup()
|
||||
node := t.O()
|
||||
f.append(node)
|
||||
if len(f.Args) == 1 && f.F.VariantReturn {
|
||||
f.F.Return = node.Return()
|
||||
}
|
||||
case itemString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.errorf("Unquoting error: %s", err)
|
||||
}
|
||||
f.append(newString(token.pos, token.val, s))
|
||||
case itemRightParen:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetFunction gets a parsed Func from the functions available on the tree's func property.
|
||||
func (t *Tree) GetFunction(name string) (v Func, ok bool) {
|
||||
for _, funcMap := range t.funcs {
|
||||
if funcMap == nil {
|
||||
continue
|
||||
}
|
||||
if v, ok = funcMap[name]; ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns a string representation of the parse tree.
|
||||
func (t *Tree) String() string {
|
||||
return t.Root.String()
|
||||
}
|
||||
Reference in New Issue
Block a user