// Copyright 2016-2020, Pulumi Corporation. // // 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 format import ( "fmt" "io" "math" "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" ) // ExpressionGenerator is an interface that can be implemented in order to generate code for semantically-analyzed HCL2 // expressions using a Formatter. type ExpressionGenerator interface { // GetPrecedence returns the precedence for the indicated expression. Lower numbers bind more tightly than higher // numbers. GetPrecedence(expr model.Expression) int // GenAnonymousFunctionExpression generates code for an AnonymousFunctionExpression. GenAnonymousFunctionExpression(w io.Writer, expr *model.AnonymousFunctionExpression) // GenBinaryOpExpression generates code for a BinaryOpExpression. GenBinaryOpExpression(w io.Writer, expr *model.BinaryOpExpression) // GenConditionalExpression generates code for a ConditionalExpression. GenConditionalExpression(w io.Writer, expr *model.ConditionalExpression) // GenForExpression generates code for a ForExpression. GenForExpression(w io.Writer, expr *model.ForExpression) // GenFunctionCallExpression generates code for a FunctionCallExpression. GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) // GenIndexExpression generates code for an IndexExpression. GenIndexExpression(w io.Writer, expr *model.IndexExpression) // GenLiteralValueExpression generates code for a LiteralValueExpression. GenLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression) // GenObjectConsExpression generates code for an ObjectConsExpression. GenObjectConsExpression(w io.Writer, expr *model.ObjectConsExpression) // GenRelativeTraversalExpression generates code for a RelativeTraversalExpression. GenRelativeTraversalExpression(w io.Writer, expr *model.RelativeTraversalExpression) // GenScopeTraversalExpression generates code for a ScopeTraversalExpression. GenScopeTraversalExpression(w io.Writer, expr *model.ScopeTraversalExpression) // GenSplatExpression generates code for a SplatExpression. GenSplatExpression(w io.Writer, expr *model.SplatExpression) // GenTemplateExpression generates code for a TemplateExpression. GenTemplateExpression(w io.Writer, expr *model.TemplateExpression) // GenTemplateJoinExpression generates code for a TemplateJoinExpression. GenTemplateJoinExpression(w io.Writer, expr *model.TemplateJoinExpression) // GenTupleConsExpression generates code for a TupleConsExpression. GenTupleConsExpression(w io.Writer, expr *model.TupleConsExpression) // GenUnaryOpExpression generates code for a UnaryOpExpression. GenUnaryOpExpression(w io.Writer, expr *model.UnaryOpExpression) } // Formatter is a convenience type that implements a number of common utilities used to emit source code. It implements // the io.Writer interface. type Formatter struct { // The current indent level as a string. Indent string // The ExpressionGenerator to use in {G,Fg}en{,f} g ExpressionGenerator } // NewFormatter creates a new emitter targeting the given io.Writer that will use the given ExpressionGenerator when // generating code. func NewFormatter(g ExpressionGenerator) *Formatter { return &Formatter{g: g} } // Indented bumps the current indentation level, invokes the given function, and then resets the indentation level to // its prior value. func (e *Formatter) Indented(f func()) { e.Indent += " " f() e.Indent = e.Indent[:len(e.Indent)-4] } // Fprint prints one or more values to the generator's output stream. func (e *Formatter) Fprint(w io.Writer, a ...interface{}) { _, err := fmt.Fprint(w, a...) contract.IgnoreError(err) } // Fprintln prints one or more values to the generator's output stream, followed by a newline. func (e *Formatter) Fprintln(w io.Writer, a ...interface{}) { e.Fprint(w, a...) e.Fprint(w, "\n") } // Fprintf prints a formatted message to the generator's output stream. func (e *Formatter) Fprintf(w io.Writer, format string, a ...interface{}) { _, err := fmt.Fprintf(w, format, a...) contract.IgnoreError(err) } func (e *Formatter) gen(w io.Writer, parentPrecedence int, rhs bool, x model.Expression) { precedence := e.g.GetPrecedence(x) switch { case precedence > parentPrecedence: // OK case precedence < parentPrecedence || rhs: _, err := fmt.Fprint(w, "(") contract.AssertNoErrorf(err, "write error") defer func() { _, err = fmt.Fprint(w, ")") contract.AssertNoErrorf(err, "write error") }() } switch x := x.(type) { case *model.AnonymousFunctionExpression: e.g.GenAnonymousFunctionExpression(w, x) case *model.BinaryOpExpression: e.g.GenBinaryOpExpression(w, x) case *model.ConditionalExpression: e.g.GenConditionalExpression(w, x) case *model.ForExpression: e.g.GenForExpression(w, x) case *model.FunctionCallExpression: e.g.GenFunctionCallExpression(w, x) case *model.IndexExpression: e.g.GenIndexExpression(w, x) case *model.LiteralValueExpression: e.g.GenLiteralValueExpression(w, x) case *model.ObjectConsExpression: e.g.GenObjectConsExpression(w, x) case *model.RelativeTraversalExpression: e.g.GenRelativeTraversalExpression(w, x) case *model.ScopeTraversalExpression: e.g.GenScopeTraversalExpression(w, x) case *model.SplatExpression: e.g.GenSplatExpression(w, x) case *model.TemplateExpression: e.g.GenTemplateExpression(w, x) case *model.TemplateJoinExpression: e.g.GenTemplateJoinExpression(w, x) case *model.TupleConsExpression: e.g.GenTupleConsExpression(w, x) case *model.UnaryOpExpression: e.g.GenUnaryOpExpression(w, x) default: contract.Failf("unexpected expression node of type %T (%v)", x, x.SyntaxNode().Range()) } } // Fgen generates code for a list of strings and expression trees. The former are written directly to the destination; // the latter are recursively generated using the appropriate gen* functions. func (e *Formatter) Fgen(w io.Writer, vs ...interface{}) { for _, v := range vs { if x, ok := v.(model.Expression); ok { e.gen(w, math.MaxInt32, false, x) } else { _, err := fmt.Fprint(w, v) contract.AssertNoErrorf(err, "write error") } } } // Fgenf generates code using a format string and its arguments. Any arguments that are BoundNode values are wrapped in // a Func that calls the appropriate recursive generation function. This allows for the composition of standard // format strings with expression/property code gen (e.e. `e.genf(w, ".apply(__arg0 => %v)", then)`, where `then` is // an expression tree). func (e *Formatter) Fgenf(w io.Writer, format string, args ...interface{}) { for i := range args { if node, ok := args[i].(model.Expression); ok { args[i] = Func(func(f fmt.State, c rune) { parentPrecedence := 0 if pp, ok := f.Precision(); ok { parentPrecedence = pp } rhs := c == 'o' e.gen(f, parentPrecedence, rhs, node) }) } } fmt.Fprintf(w, format, args...) }