1// Copyright 2018 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package debug provides tools to print a parsed expression graph and 16// adorn each expression element with additional metadata. 17package debug 18 19import ( 20 "bytes" 21 "fmt" 22 "strconv" 23 "strings" 24 25 exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" 26) 27 28// Adorner returns debug metadata that will be tacked on to the string 29// representation of an expression. 30type Adorner interface { 31 // GetMetadata for the input context. 32 GetMetadata(ctx interface{}) string 33} 34 35// Writer manages writing expressions to an internal string. 36type Writer interface { 37 fmt.Stringer 38 39 // Buffer pushes an expression into an internal queue of expressions to 40 // write to a string. 41 Buffer(e *exprpb.Expr) 42} 43 44type emptyDebugAdorner struct { 45} 46 47var emptyAdorner Adorner = &emptyDebugAdorner{} 48 49func (a *emptyDebugAdorner) GetMetadata(e interface{}) string { 50 return "" 51} 52 53// ToDebugString gives the unadorned string representation of the Expr. 54func ToDebugString(e *exprpb.Expr) string { 55 return ToAdornedDebugString(e, emptyAdorner) 56} 57 58// ToAdornedDebugString gives the adorned string representation of the Expr. 59func ToAdornedDebugString(e *exprpb.Expr, adorner Adorner) string { 60 w := newDebugWriter(adorner) 61 w.Buffer(e) 62 return w.String() 63} 64 65// debugWriter is used to print out pretty-printed debug strings. 66type debugWriter struct { 67 adorner Adorner 68 buffer bytes.Buffer 69 indent int 70 lineStart bool 71} 72 73func newDebugWriter(a Adorner) *debugWriter { 74 return &debugWriter{ 75 adorner: a, 76 indent: 0, 77 lineStart: true, 78 } 79} 80 81func (w *debugWriter) Buffer(e *exprpb.Expr) { 82 if e == nil { 83 return 84 } 85 switch e.ExprKind.(type) { 86 case *exprpb.Expr_ConstExpr: 87 w.append(formatLiteral(e.GetConstExpr())) 88 case *exprpb.Expr_IdentExpr: 89 w.append(e.GetIdentExpr().Name) 90 case *exprpb.Expr_SelectExpr: 91 w.appendSelect(e.GetSelectExpr()) 92 case *exprpb.Expr_CallExpr: 93 w.appendCall(e.GetCallExpr()) 94 case *exprpb.Expr_ListExpr: 95 w.appendList(e.GetListExpr()) 96 case *exprpb.Expr_StructExpr: 97 w.appendStruct(e.GetStructExpr()) 98 case *exprpb.Expr_ComprehensionExpr: 99 w.appendComprehension(e.GetComprehensionExpr()) 100 } 101 w.adorn(e) 102} 103 104func (w *debugWriter) appendSelect(sel *exprpb.Expr_Select) { 105 w.Buffer(sel.Operand) 106 w.append(".") 107 w.append(sel.Field) 108 if sel.TestOnly { 109 w.append("~test-only~") 110 } 111} 112 113func (w *debugWriter) appendCall(call *exprpb.Expr_Call) { 114 if call.Target != nil { 115 w.Buffer(call.Target) 116 w.append(".") 117 } 118 w.append(call.Function) 119 w.append("(") 120 if len(call.GetArgs()) > 0 { 121 w.addIndent() 122 w.appendLine() 123 for i, arg := range call.Args { 124 if i > 0 { 125 w.append(",") 126 w.appendLine() 127 } 128 w.Buffer(arg) 129 } 130 w.removeIndent() 131 w.appendLine() 132 } 133 w.append(")") 134} 135 136func (w *debugWriter) appendList(list *exprpb.Expr_CreateList) { 137 w.append("[") 138 if len(list.GetElements()) > 0 { 139 w.appendLine() 140 w.addIndent() 141 for i, elem := range list.Elements { 142 if i > 0 { 143 w.append(",") 144 w.appendLine() 145 } 146 w.Buffer(elem) 147 } 148 w.removeIndent() 149 w.appendLine() 150 } 151 w.append("]") 152} 153 154func (w *debugWriter) appendStruct(obj *exprpb.Expr_CreateStruct) { 155 if obj.MessageName != "" { 156 w.appendObject(obj) 157 } else { 158 w.appendMap(obj) 159 } 160} 161 162func (w *debugWriter) appendObject(obj *exprpb.Expr_CreateStruct) { 163 w.append(obj.MessageName) 164 w.append("{") 165 if len(obj.Entries) > 0 { 166 w.appendLine() 167 w.addIndent() 168 for i, entry := range obj.Entries { 169 if i > 0 { 170 w.append(",") 171 w.appendLine() 172 } 173 w.append(entry.GetFieldKey()) 174 w.append(":") 175 w.Buffer(entry.Value) 176 w.adorn(entry) 177 } 178 w.removeIndent() 179 w.appendLine() 180 } 181 w.append("}") 182} 183 184func (w *debugWriter) appendMap(obj *exprpb.Expr_CreateStruct) { 185 w.append("{") 186 if len(obj.Entries) > 0 { 187 w.appendLine() 188 w.addIndent() 189 for i, entry := range obj.Entries { 190 if i > 0 { 191 w.append(",") 192 w.appendLine() 193 } 194 w.Buffer(entry.GetMapKey()) 195 w.append(":") 196 w.Buffer(entry.Value) 197 w.adorn(entry) 198 } 199 w.removeIndent() 200 w.appendLine() 201 } 202 w.append("}") 203} 204 205func (w *debugWriter) appendComprehension(comprehension *exprpb.Expr_Comprehension) { 206 w.append("__comprehension__(") 207 w.addIndent() 208 w.appendLine() 209 w.append("// Variable") 210 w.appendLine() 211 w.append(comprehension.IterVar) 212 w.append(",") 213 w.appendLine() 214 w.append("// Target") 215 w.appendLine() 216 w.Buffer(comprehension.IterRange) 217 w.append(",") 218 w.appendLine() 219 w.append("// Accumulator") 220 w.appendLine() 221 w.append(comprehension.AccuVar) 222 w.append(",") 223 w.appendLine() 224 w.append("// Init") 225 w.appendLine() 226 w.Buffer(comprehension.AccuInit) 227 w.append(",") 228 w.appendLine() 229 w.append("// LoopCondition") 230 w.appendLine() 231 w.Buffer(comprehension.LoopCondition) 232 w.append(",") 233 w.appendLine() 234 w.append("// LoopStep") 235 w.appendLine() 236 w.Buffer(comprehension.LoopStep) 237 w.append(",") 238 w.appendLine() 239 w.append("// Result") 240 w.appendLine() 241 w.Buffer(comprehension.Result) 242 w.append(")") 243 w.removeIndent() 244} 245 246func formatLiteral(c *exprpb.Constant) string { 247 switch c.ConstantKind.(type) { 248 case *exprpb.Constant_BoolValue: 249 return fmt.Sprintf("%t", c.GetBoolValue()) 250 case *exprpb.Constant_BytesValue: 251 return fmt.Sprintf("b\"%s\"", string(c.GetBytesValue())) 252 case *exprpb.Constant_DoubleValue: 253 return fmt.Sprintf("%v", c.GetDoubleValue()) 254 case *exprpb.Constant_Int64Value: 255 return fmt.Sprintf("%d", c.GetInt64Value()) 256 case *exprpb.Constant_StringValue: 257 return strconv.Quote(c.GetStringValue()) 258 case *exprpb.Constant_Uint64Value: 259 return fmt.Sprintf("%du", c.GetUint64Value()) 260 case *exprpb.Constant_NullValue: 261 return "null" 262 default: 263 panic("Unknown constant type") 264 } 265} 266 267func (w *debugWriter) append(s string) { 268 w.doIndent() 269 w.buffer.WriteString(s) 270} 271 272func (w *debugWriter) appendFormat(f string, args ...interface{}) { 273 w.append(fmt.Sprintf(f, args...)) 274} 275 276func (w *debugWriter) doIndent() { 277 if w.lineStart { 278 w.lineStart = false 279 w.buffer.WriteString(strings.Repeat(" ", w.indent)) 280 } 281} 282 283func (w *debugWriter) adorn(e interface{}) { 284 w.append(w.adorner.GetMetadata(e)) 285} 286 287func (w *debugWriter) appendLine() { 288 w.buffer.WriteString("\n") 289 w.lineStart = true 290} 291 292func (w *debugWriter) addIndent() { 293 w.indent++ 294} 295 296func (w *debugWriter) removeIndent() { 297 w.indent-- 298 if w.indent < 0 { 299 panic("negative indent") 300 } 301} 302 303func (w *debugWriter) String() string { 304 return w.buffer.String() 305} 306