| // 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" |
| |
| exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" |
| ) |
| |
| // 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 interface{}) 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 *exprpb.Expr) |
| } |
| |
| type emptyDebugAdorner struct { |
| } |
| |
| var emptyAdorner Adorner = &emptyDebugAdorner{} |
| |
| func (a *emptyDebugAdorner) GetMetadata(e interface{}) string { |
| return "" |
| } |
| |
| // ToDebugString gives the unadorned string representation of the Expr. |
| func ToDebugString(e *exprpb.Expr) string { |
| return ToAdornedDebugString(e, emptyAdorner) |
| } |
| |
| // ToAdornedDebugString gives the adorned string representation of the Expr. |
| func ToAdornedDebugString(e *exprpb.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 *exprpb.Expr) { |
| if e == nil { |
| return |
| } |
| switch e.ExprKind.(type) { |
| case *exprpb.Expr_ConstExpr: |
| w.append(formatLiteral(e.GetConstExpr())) |
| case *exprpb.Expr_IdentExpr: |
| w.append(e.GetIdentExpr().Name) |
| case *exprpb.Expr_SelectExpr: |
| w.appendSelect(e.GetSelectExpr()) |
| case *exprpb.Expr_CallExpr: |
| w.appendCall(e.GetCallExpr()) |
| case *exprpb.Expr_ListExpr: |
| w.appendList(e.GetListExpr()) |
| case *exprpb.Expr_StructExpr: |
| w.appendStruct(e.GetStructExpr()) |
| case *exprpb.Expr_ComprehensionExpr: |
| w.appendComprehension(e.GetComprehensionExpr()) |
| } |
| w.adorn(e) |
| } |
| |
| func (w *debugWriter) appendSelect(sel *exprpb.Expr_Select) { |
| w.Buffer(sel.Operand) |
| w.append(".") |
| w.append(sel.Field) |
| if sel.TestOnly { |
| w.append("~test-only~") |
| } |
| } |
| |
| func (w *debugWriter) appendCall(call *exprpb.Expr_Call) { |
| if call.Target != nil { |
| w.Buffer(call.Target) |
| w.append(".") |
| } |
| w.append(call.Function) |
| w.append("(") |
| if len(call.GetArgs()) > 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 *exprpb.Expr_CreateList) { |
| w.append("[") |
| if len(list.GetElements()) > 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 *exprpb.Expr_CreateStruct) { |
| if obj.MessageName != "" { |
| w.appendObject(obj) |
| } else { |
| w.appendMap(obj) |
| } |
| } |
| |
| func (w *debugWriter) appendObject(obj *exprpb.Expr_CreateStruct) { |
| w.append(obj.MessageName) |
| w.append("{") |
| if len(obj.Entries) > 0 { |
| w.appendLine() |
| w.addIndent() |
| for i, entry := range obj.Entries { |
| if i > 0 { |
| w.append(",") |
| w.appendLine() |
| } |
| w.append(entry.GetFieldKey()) |
| w.append(":") |
| w.Buffer(entry.Value) |
| w.adorn(entry) |
| } |
| w.removeIndent() |
| w.appendLine() |
| } |
| w.append("}") |
| } |
| |
| func (w *debugWriter) appendMap(obj *exprpb.Expr_CreateStruct) { |
| w.append("{") |
| if len(obj.Entries) > 0 { |
| w.appendLine() |
| w.addIndent() |
| for i, entry := range obj.Entries { |
| if i > 0 { |
| w.append(",") |
| w.appendLine() |
| } |
| w.Buffer(entry.GetMapKey()) |
| w.append(":") |
| w.Buffer(entry.Value) |
| w.adorn(entry) |
| } |
| w.removeIndent() |
| w.appendLine() |
| } |
| w.append("}") |
| } |
| |
| func (w *debugWriter) appendComprehension(comprehension *exprpb.Expr_Comprehension) { |
| w.append("__comprehension__(") |
| w.addIndent() |
| w.appendLine() |
| w.append("// Variable") |
| w.appendLine() |
| w.append(comprehension.IterVar) |
| 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 *exprpb.Constant) string { |
| switch c.ConstantKind.(type) { |
| case *exprpb.Constant_BoolValue: |
| return fmt.Sprintf("%t", c.GetBoolValue()) |
| case *exprpb.Constant_BytesValue: |
| return fmt.Sprintf("b\"%s\"", string(c.GetBytesValue())) |
| case *exprpb.Constant_DoubleValue: |
| return fmt.Sprintf("%v", c.GetDoubleValue()) |
| case *exprpb.Constant_Int64Value: |
| return fmt.Sprintf("%d", c.GetInt64Value()) |
| case *exprpb.Constant_StringValue: |
| return strconv.Quote(c.GetStringValue()) |
| case *exprpb.Constant_Uint64Value: |
| return fmt.Sprintf("%du", c.GetUint64Value()) |
| case *exprpb.Constant_NullValue: |
| 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 ...interface{}) { |
| 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 interface{}) { |
| 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() |
| } |