2020-11-19 06:17:00 -06:00
|
|
|
// 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
|
2020-12-14 13:21:16 -06:00
|
|
|
Name string // Without the $ or {}
|
2020-11-19 06:17:00 -06:00
|
|
|
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
|
2022-07-14 08:18:12 -05:00
|
|
|
// TypeNoData is a no data response without a known data type.
|
|
|
|
TypeNoData
|
2020-11-19 06:17:00 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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"
|
2022-07-14 08:18:12 -05:00
|
|
|
case TypeNoData:
|
|
|
|
return "noData"
|
2020-11-19 06:17:00 -06:00
|
|
|
default:
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
}
|