mirror of https://github.com/prometheus/prometheus
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
4.6 KiB
166 lines
4.6 KiB
// 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) |
|
}
|
|
|