| // Copyright 2018 Google LLC |
| // |
| // 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 debug provides tools to print a parsed expression graph and |
| // adorn each expression element with additional metadata. |
| package debug |
| |
| import ( |
| "bytes" |
| "fmt" |
| "strconv" |
| "strings" |
| |
| "github.com/google/cel-go/common/ast" |
| "github.com/google/cel-go/common/types" |
| "github.com/google/cel-go/common/types/ref" |
| ) |
| |
| // Adorner returns debug metadata that will be tacked on to the string |
| // representation of an expression. |
| type Adorner interface { |
| // GetMetadata for the input context. |
| GetMetadata(ctx any) string |
| } |
| |
| // Writer manages writing expressions to an internal string. |
| type Writer interface { |
| fmt.Stringer |
| |
| // Buffer pushes an expression into an internal queue of expressions to |
| // write to a string. |
| Buffer(e ast.Expr) |
| } |
| |
| type emptyDebugAdorner struct { |
| } |
| |
| var emptyAdorner Adorner = &emptyDebugAdorner{} |
| |
| func (a *emptyDebugAdorner) GetMetadata(e any) string { |
| return "" |
| } |
| |
| // ToDebugString gives the unadorned string representation of the Expr. |
| func ToDebugString(e ast.Expr) string { |
| return ToAdornedDebugString(e, emptyAdorner) |
| } |
| |
| // ToAdornedDebugString gives the adorned string representation of the Expr. |
| func ToAdornedDebugString(e ast.Expr, adorner Adorner) string { |
| w := newDebugWriter(adorner) |
| w.Buffer(e) |
| return w.String() |
| } |
| |
| // debugWriter is used to print out pretty-printed debug strings. |
| type debugWriter struct { |
| adorner Adorner |
| buffer bytes.Buffer |
| indent int |
| lineStart bool |
| } |
| |
| func newDebugWriter(a Adorner) *debugWriter { |
| return &debugWriter{ |
| adorner: a, |
| indent: 0, |
| lineStart: true, |
| } |
| } |
| |
| func (w *debugWriter) Buffer(e ast.Expr) { |
| if e == nil { |
| return |
| } |
| switch e.Kind() { |
| case ast.LiteralKind: |
| w.append(formatLiteral(e.AsLiteral())) |
| case ast.IdentKind: |
| w.append(e.AsIdent()) |
| case ast.SelectKind: |
| w.appendSelect(e.AsSelect()) |
| case ast.CallKind: |
| w.appendCall(e.AsCall()) |
| case ast.ListKind: |
| w.appendList(e.AsList()) |
| case ast.MapKind: |
| w.appendMap(e.AsMap()) |
| case ast.StructKind: |
| w.appendStruct(e.AsStruct()) |
| case ast.ComprehensionKind: |
| w.appendComprehension(e.AsComprehension()) |
| } |
| w.adorn(e) |
| } |
| |
| func (w *debugWriter) appendSelect(sel ast.SelectExpr) { |
| w.Buffer(sel.Operand()) |
| w.append(".") |
| w.append(sel.FieldName()) |
| if sel.IsTestOnly() { |
| w.append("~test-only~") |
| } |
| } |
| |
| func (w *debugWriter) appendCall(call ast.CallExpr) { |
| if call.IsMemberFunction() { |
| w.Buffer(call.Target()) |
| w.append(".") |
| } |
| w.append(call.FunctionName()) |
| w.append("(") |
| if len(call.Args()) > 0 { |
| w.addIndent() |
| w.appendLine() |
| for i, arg := range call.Args() { |
| if i > 0 { |
| w.append(",") |
| w.appendLine() |
| } |
| w.Buffer(arg) |
| } |
| w.removeIndent() |
| w.appendLine() |
| } |
| w.append(")") |
| } |
| |
| func (w *debugWriter) appendList(list ast.ListExpr) { |
| w.append("[") |
| if len(list.Elements()) > 0 { |
| w.appendLine() |
| w.addIndent() |
| for i, elem := range list.Elements() { |
| if i > 0 { |
| w.append(",") |
| w.appendLine() |
| } |
| w.Buffer(elem) |
| } |
| w.removeIndent() |
| w.appendLine() |
| } |
| w.append("]") |
| } |
| |
| func (w *debugWriter) appendStruct(obj ast.StructExpr) { |
| w.append(obj.TypeName()) |
| w.append("{") |
| if len(obj.Fields()) > 0 { |
| w.appendLine() |
| w.addIndent() |
| for i, f := range obj.Fields() { |
| field := f.AsStructField() |
| if i > 0 { |
| w.append(",") |
| w.appendLine() |
| } |
| if field.IsOptional() { |
| w.append("?") |
| } |
| w.append(field.Name()) |
| w.append(":") |
| w.Buffer(field.Value()) |
| w.adorn(f) |
| } |
| w.removeIndent() |
| w.appendLine() |
| } |
| w.append("}") |
| } |
| |
| func (w *debugWriter) appendMap(m ast.MapExpr) { |
| w.append("{") |
| if m.Size() > 0 { |
| w.appendLine() |
| w.addIndent() |
| for i, e := range m.Entries() { |
| entry := e.AsMapEntry() |
| if i > 0 { |
| w.append(",") |
| w.appendLine() |
| } |
| if entry.IsOptional() { |
| w.append("?") |
| } |
| w.Buffer(entry.Key()) |
| w.append(":") |
| w.Buffer(entry.Value()) |
| w.adorn(e) |
| } |
| w.removeIndent() |
| w.appendLine() |
| } |
| w.append("}") |
| } |
| |
| func (w *debugWriter) appendComprehension(comprehension ast.ComprehensionExpr) { |
| w.append("__comprehension__(") |
| w.addIndent() |
| w.appendLine() |
| w.append("// Variable") |
| w.appendLine() |
| w.append(comprehension.IterVar()) |
| w.append(",") |
| w.appendLine() |
| if comprehension.HasIterVar2() { |
| w.append(comprehension.IterVar2()) |
| w.append(",") |
| w.appendLine() |
| } |
| w.append("// Target") |
| w.appendLine() |
| w.Buffer(comprehension.IterRange()) |
| w.append(",") |
| w.appendLine() |
| w.append("// Accumulator") |
| w.appendLine() |
| w.append(comprehension.AccuVar()) |
| w.append(",") |
| w.appendLine() |
| w.append("// Init") |
| w.appendLine() |
| w.Buffer(comprehension.AccuInit()) |
| w.append(",") |
| w.appendLine() |
| w.append("// LoopCondition") |
| w.appendLine() |
| w.Buffer(comprehension.LoopCondition()) |
| w.append(",") |
| w.appendLine() |
| w.append("// LoopStep") |
| w.appendLine() |
| w.Buffer(comprehension.LoopStep()) |
| w.append(",") |
| w.appendLine() |
| w.append("// Result") |
| w.appendLine() |
| w.Buffer(comprehension.Result()) |
| w.append(")") |
| w.removeIndent() |
| } |
| |
| func formatLiteral(c ref.Val) string { |
| switch v := c.(type) { |
| case types.Bool: |
| return fmt.Sprintf("%t", v) |
| case types.Bytes: |
| return fmt.Sprintf("b%s", strconv.Quote(string(v))) |
| case types.Double: |
| return fmt.Sprintf("%v", float64(v)) |
| case types.Int: |
| return fmt.Sprintf("%d", int64(v)) |
| case types.String: |
| return strconv.Quote(string(v)) |
| case types.Uint: |
| return fmt.Sprintf("%du", uint64(v)) |
| case types.Null: |
| return "null" |
| default: |
| panic("Unknown constant type") |
| } |
| } |
| |
| func (w *debugWriter) append(s string) { |
| w.doIndent() |
| w.buffer.WriteString(s) |
| } |
| |
| func (w *debugWriter) appendFormat(f string, args ...any) { |
| w.append(fmt.Sprintf(f, args...)) |
| } |
| |
| func (w *debugWriter) doIndent() { |
| if w.lineStart { |
| w.lineStart = false |
| w.buffer.WriteString(strings.Repeat(" ", w.indent)) |
| } |
| } |
| |
| func (w *debugWriter) adorn(e any) { |
| w.append(w.adorner.GetMetadata(e)) |
| } |
| |
| func (w *debugWriter) appendLine() { |
| w.buffer.WriteString("\n") |
| w.lineStart = true |
| } |
| |
| func (w *debugWriter) addIndent() { |
| w.indent++ |
| } |
| |
| func (w *debugWriter) removeIndent() { |
| w.indent-- |
| if w.indent < 0 { |
| panic("negative indent") |
| } |
| } |
| |
| func (w *debugWriter) String() string { |
| return w.buffer.String() |
| } |
| |
| type idAdorner struct{} |
| |
| func (a *idAdorner) GetMetadata(elem any) string { |
| e, isExpr := elem.(ast.Expr) |
| if !isExpr { |
| return "" |
| } |
| return fmt.Sprintf("@id:%d ", e.ID()) |
| } |
| |
| // ToDebugStringWithIDs returns a string representation with AST node IDs. |
| func ToDebugStringWithIDs(e ast.Expr) string { |
| return ToAdornedDebugString(e, &idAdorner{}) |
| } |