167 lines
4.6 KiB
Go
167 lines
4.6 KiB
Go
|
// Copyright 2022 The Prometheus Authors
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package parser
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// Approach
|
||
|
// --------
|
||
|
// When a PromQL query is parsed, it is converted into PromQL AST,
|
||
|
// which is a nested structure of nodes. Each node has a depth/level
|
||
|
// (distance from the root), that is passed by its parent.
|
||
|
//
|
||
|
// While prettifying, a Node considers 2 things:
|
||
|
// 1. Did the current Node's parent add a new line?
|
||
|
// 2. Does the current Node needs to be prettified?
|
||
|
//
|
||
|
// The level of a Node determines if it should be indented or not.
|
||
|
// The answer to the 1 is NO if the level passed is 0. This means, the
|
||
|
// parent Node did not apply a new line, so the current Node must not
|
||
|
// apply any indentation as prefix.
|
||
|
// If level > 1, a new line is applied by the parent. So, the current Node
|
||
|
// should prefix an indentation before writing any of its content. This indentation
|
||
|
// will be ([level/depth of current Node] * " ").
|
||
|
//
|
||
|
// The answer to 2 is YES if the normalized length of the current Node exceeds
|
||
|
// the maxCharactersPerLine limit. Hence, it applies the indentation equal to
|
||
|
// its depth and increments the level by 1 before passing down the child.
|
||
|
// If the answer is NO, the current Node returns the normalized string value of itself.
|
||
|
|
||
|
var maxCharactersPerLine = 100
|
||
|
|
||
|
func Prettify(n Node) string {
|
||
|
return n.Pretty(0)
|
||
|
}
|
||
|
|
||
|
func (e *AggregateExpr) Pretty(level int) string {
|
||
|
s := indent(level)
|
||
|
if !needsSplit(e) {
|
||
|
s += e.String()
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
s += e.getAggOpStr()
|
||
|
s += "(\n"
|
||
|
|
||
|
if e.Op.IsAggregatorWithParam() {
|
||
|
s += fmt.Sprintf("%s,\n", e.Param.Pretty(level+1))
|
||
|
}
|
||
|
s += fmt.Sprintf("%s\n%s)", e.Expr.Pretty(level+1), indent(level))
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func (e *BinaryExpr) Pretty(level int) string {
|
||
|
s := indent(level)
|
||
|
if !needsSplit(e) {
|
||
|
s += e.String()
|
||
|
return s
|
||
|
}
|
||
|
returnBool := ""
|
||
|
if e.ReturnBool {
|
||
|
returnBool = " bool"
|
||
|
}
|
||
|
|
||
|
matching := e.getMatchingStr()
|
||
|
return fmt.Sprintf("%s\n%s%s%s%s\n%s", e.LHS.Pretty(level+1), indent(level), e.Op, returnBool, matching, e.RHS.Pretty(level+1))
|
||
|
}
|
||
|
|
||
|
func (e *Call) Pretty(level int) string {
|
||
|
s := indent(level)
|
||
|
if !needsSplit(e) {
|
||
|
s += e.String()
|
||
|
return s
|
||
|
}
|
||
|
s += fmt.Sprintf("%s(\n%s\n%s)", e.Func.Name, e.Args.Pretty(level+1), indent(level))
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func (e *EvalStmt) Pretty(_ int) string {
|
||
|
return "EVAL " + e.Expr.String()
|
||
|
}
|
||
|
|
||
|
func (e Expressions) Pretty(level int) string {
|
||
|
// Do not prefix the indent since respective nodes will indent itself.
|
||
|
s := ""
|
||
|
for i := range e {
|
||
|
s += fmt.Sprintf("%s,\n", e[i].Pretty(level))
|
||
|
}
|
||
|
return s[:len(s)-2]
|
||
|
}
|
||
|
|
||
|
func (e *ParenExpr) Pretty(level int) string {
|
||
|
s := indent(level)
|
||
|
if !needsSplit(e) {
|
||
|
s += e.String()
|
||
|
return s
|
||
|
}
|
||
|
return fmt.Sprintf("%s(\n%s\n%s)", s, e.Expr.Pretty(level+1), indent(level))
|
||
|
}
|
||
|
|
||
|
func (e *StepInvariantExpr) Pretty(level int) string {
|
||
|
return e.Expr.Pretty(level)
|
||
|
}
|
||
|
|
||
|
func (e *MatrixSelector) Pretty(level int) string {
|
||
|
return getCommonPrefixIndent(level, e)
|
||
|
}
|
||
|
|
||
|
func (e *SubqueryExpr) Pretty(level int) string {
|
||
|
if !needsSplit(e) {
|
||
|
return e.String()
|
||
|
}
|
||
|
return fmt.Sprintf("%s%s", e.Expr.Pretty(level), e.getSubqueryTimeSuffix())
|
||
|
}
|
||
|
|
||
|
func (e *VectorSelector) Pretty(level int) string {
|
||
|
return getCommonPrefixIndent(level, e)
|
||
|
}
|
||
|
|
||
|
func (e *NumberLiteral) Pretty(level int) string {
|
||
|
return getCommonPrefixIndent(level, e)
|
||
|
}
|
||
|
|
||
|
func (e *StringLiteral) Pretty(level int) string {
|
||
|
return getCommonPrefixIndent(level, e)
|
||
|
}
|
||
|
|
||
|
func (e *UnaryExpr) Pretty(level int) string {
|
||
|
child := e.Expr.Pretty(level)
|
||
|
// Remove the indent prefix from child since we attach the prefix indent before Op.
|
||
|
child = strings.TrimSpace(child)
|
||
|
return fmt.Sprintf("%s%s%s", indent(level), e.Op, child)
|
||
|
}
|
||
|
|
||
|
func getCommonPrefixIndent(level int, current Node) string {
|
||
|
return fmt.Sprintf("%s%s", indent(level), current.String())
|
||
|
}
|
||
|
|
||
|
// needsSplit normalizes the node and then checks if the node needs any split.
|
||
|
// This is necessary to remove any trailing whitespaces.
|
||
|
func needsSplit(n Node) bool {
|
||
|
if n == nil {
|
||
|
return false
|
||
|
}
|
||
|
return len(n.String()) > maxCharactersPerLine
|
||
|
}
|
||
|
|
||
|
const indentString = " "
|
||
|
|
||
|
// indent adds the indentString n number of times.
|
||
|
func indent(n int) string {
|
||
|
return strings.Repeat(indentString, n)
|
||
|
}
|