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