github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/python/gen_program_expressions.go (about)

     1  // nolint: goconst
     2  package python
     3  
     4  import (
     5  	"bufio"
     6  	"bytes"
     7  	"fmt"
     8  	"io"
     9  	"math/big"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/hcl/v2"
    13  	"github.com/hashicorp/hcl/v2/hclsyntax"
    14  	"github.com/zclconf/go-cty/cty"
    15  
    16  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
    17  	"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
    18  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    19  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    20  )
    21  
    22  type nameInfo int
    23  
    24  func (nameInfo) Format(name string) string {
    25  	return PyName(name)
    26  }
    27  
    28  func (g *generator) lowerExpression(expr model.Expression, typ model.Type) (model.Expression, []*quoteTemp) {
    29  	// TODO(pdg): diagnostics
    30  
    31  	expr = pcl.RewritePropertyReferences(expr)
    32  	expr, diags := pcl.RewriteApplies(expr, nameInfo(0), false)
    33  	expr, lowerProxyDiags := g.lowerProxyApplies(expr)
    34  	expr, convertDiags := pcl.RewriteConversions(expr, typ)
    35  	expr, quotes, quoteDiags := g.rewriteQuotes(expr)
    36  
    37  	diags = diags.Extend(lowerProxyDiags)
    38  	diags = diags.Extend(convertDiags)
    39  	diags = diags.Extend(quoteDiags)
    40  
    41  	g.diagnostics = g.diagnostics.Extend(diags)
    42  
    43  	return expr, quotes
    44  }
    45  
    46  func (g *generator) GetPrecedence(expr model.Expression) int {
    47  	// Precedence is taken from https://docs.python.org/3/reference/expressions.html#operator-precedence.
    48  	switch expr := expr.(type) {
    49  	case *model.AnonymousFunctionExpression:
    50  		return 1
    51  	case *model.ConditionalExpression:
    52  		return 2
    53  	case *model.BinaryOpExpression:
    54  		switch expr.Operation {
    55  		case hclsyntax.OpLogicalOr:
    56  			return 3
    57  		case hclsyntax.OpLogicalAnd:
    58  			return 4
    59  		case hclsyntax.OpGreaterThan, hclsyntax.OpGreaterThanOrEqual, hclsyntax.OpLessThan, hclsyntax.OpLessThanOrEqual,
    60  			hclsyntax.OpEqual, hclsyntax.OpNotEqual:
    61  			return 6
    62  		case hclsyntax.OpAdd, hclsyntax.OpSubtract:
    63  			return 11
    64  		case hclsyntax.OpMultiply, hclsyntax.OpDivide, hclsyntax.OpModulo:
    65  			return 12
    66  		default:
    67  			contract.Failf("unexpected binary expression %v", expr)
    68  		}
    69  	case *model.UnaryOpExpression:
    70  		return 13
    71  	case *model.FunctionCallExpression, *model.IndexExpression, *model.RelativeTraversalExpression,
    72  		*model.TemplateJoinExpression:
    73  		return 16
    74  	case *model.ForExpression, *model.ObjectConsExpression, *model.SplatExpression, *model.TupleConsExpression:
    75  		return 17
    76  	case *model.LiteralValueExpression, *model.ScopeTraversalExpression, *model.TemplateExpression:
    77  		return 18
    78  	default:
    79  		contract.Failf("unexpected expression %v of type %T", expr, expr)
    80  	}
    81  	return 0
    82  }
    83  
    84  func (g *generator) GenAnonymousFunctionExpression(w io.Writer, expr *model.AnonymousFunctionExpression) {
    85  	g.Fgen(w, "lambda")
    86  	for i, p := range expr.Signature.Parameters {
    87  		if i > 0 {
    88  			g.Fgen(w, ",")
    89  		}
    90  		g.Fgenf(w, " %s", p.Name)
    91  	}
    92  
    93  	g.Fgenf(w, ": %.v", expr.Body)
    94  }
    95  
    96  func (g *generator) GenBinaryOpExpression(w io.Writer, expr *model.BinaryOpExpression) {
    97  	opstr, precedence := "", g.GetPrecedence(expr)
    98  	switch expr.Operation {
    99  	case hclsyntax.OpAdd:
   100  		opstr = "+"
   101  	case hclsyntax.OpDivide:
   102  		opstr = "/"
   103  	case hclsyntax.OpEqual:
   104  		opstr = "=="
   105  	case hclsyntax.OpGreaterThan:
   106  		opstr = ">"
   107  	case hclsyntax.OpGreaterThanOrEqual:
   108  		opstr = ">="
   109  	case hclsyntax.OpLessThan:
   110  		opstr = "<"
   111  	case hclsyntax.OpLessThanOrEqual:
   112  		opstr = "<="
   113  	case hclsyntax.OpLogicalAnd:
   114  		opstr = "and"
   115  	case hclsyntax.OpLogicalOr:
   116  		opstr = "or"
   117  	case hclsyntax.OpModulo:
   118  		opstr = "%"
   119  	case hclsyntax.OpMultiply:
   120  		opstr = "*"
   121  	case hclsyntax.OpNotEqual:
   122  		opstr = "!="
   123  	case hclsyntax.OpSubtract:
   124  		opstr = "-"
   125  	default:
   126  		opstr, precedence = ",", 0
   127  	}
   128  
   129  	g.Fgenf(w, "%.[1]*[2]v %[3]v %.[1]*[4]o", precedence, expr.LeftOperand, opstr, expr.RightOperand)
   130  }
   131  
   132  func (g *generator) GenConditionalExpression(w io.Writer, expr *model.ConditionalExpression) {
   133  	g.Fgenf(w, "%.2v if %.2v else %.2v", expr.TrueResult, expr.Condition, expr.FalseResult)
   134  }
   135  
   136  func (g *generator) GenForExpression(w io.Writer, expr *model.ForExpression) {
   137  	close := "]"
   138  	if expr.Key != nil {
   139  		// Dictionary comprehension
   140  		//
   141  		// TODO(pdg): grouping
   142  		g.Fgenf(w, "{%.v: %.v", expr.Key, expr.Value)
   143  		close = "}"
   144  	} else {
   145  		// List comprehension
   146  		g.Fgenf(w, "[%.v", expr.Value)
   147  	}
   148  
   149  	if expr.KeyVariable == nil {
   150  		g.Fgenf(w, " for %v in %.v", expr.ValueVariable.Name, expr.Collection)
   151  	} else {
   152  		g.Fgenf(w, " for %v, %v in %.v", expr.KeyVariable.Name, expr.ValueVariable.Name, expr.Collection)
   153  	}
   154  
   155  	if expr.Condition != nil {
   156  		g.Fgenf(w, " if %.v", expr.Condition)
   157  	}
   158  
   159  	g.Fprint(w, close)
   160  }
   161  
   162  func (g *generator) genApply(w io.Writer, expr *model.FunctionCallExpression) {
   163  	// Extract the list of outputs and the continuation expression from the `__apply` arguments.
   164  	applyArgs, then := pcl.ParseApplyCall(expr)
   165  
   166  	if len(applyArgs) == 1 {
   167  		// If we only have a single output, just generate a normal `.apply`.
   168  		g.Fgenf(w, "%.16v.apply(%.v)", applyArgs[0], then)
   169  	} else {
   170  		// Otherwise, generate a call to `pulumi.all([]).apply()`.
   171  		g.Fgen(w, "pulumi.Output.all(")
   172  		for i, o := range applyArgs {
   173  			if i > 0 {
   174  				g.Fgen(w, ", ")
   175  			}
   176  			g.Fgenf(w, "%.v", o)
   177  		}
   178  		g.Fgenf(w, ").apply(%.v)", then)
   179  	}
   180  }
   181  
   182  // functionName computes the Python package, module, and name for the given function token.
   183  func functionName(tokenArg model.Expression) (string, string, string, hcl.Diagnostics) {
   184  	token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString()
   185  	tokenRange := tokenArg.SyntaxNode().Range()
   186  
   187  	// Compute the resource type from the Pulumi type token.
   188  	pkg, module, member, diagnostics := pcl.DecomposeToken(token, tokenRange)
   189  	return makeValidIdentifier(pkg), strings.Replace(module, "/", ".", -1), title(member), diagnostics
   190  }
   191  
   192  var functionImports = map[string][]string{
   193  	"fileArchive":      {"pulumi"},
   194  	"remoteArchive":    {"pulumi"},
   195  	"assetArchive":     {"pulumi"},
   196  	"fileAsset":        {"pulumi"},
   197  	"stringAsset":      {"pulumi"},
   198  	"remoteAsset":      {"pulumi"},
   199  	"filebase64":       {"base64"},
   200  	"filebase64sha256": {"base64", "hashlib"},
   201  	"readDir":          {"os"},
   202  	"toBase64":         {"base64"},
   203  	"fromBase64":       {"base64"},
   204  	"toJSON":           {"json"},
   205  	"sha1":             {"hashlib"},
   206  	"stack":            {"pulumi"},
   207  	"project":          {"pulumi"},
   208  	"cwd":              {"os"},
   209  }
   210  
   211  func (g *generator) getFunctionImports(x *model.FunctionCallExpression) []string {
   212  	if x.Name != pcl.Invoke {
   213  		return functionImports[x.Name]
   214  	}
   215  
   216  	pkg, _, _, diags := functionName(x.Args[0])
   217  	contract.Assert(len(diags) == 0)
   218  	return []string{"pulumi_" + pkg}
   219  }
   220  
   221  func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) {
   222  	switch expr.Name {
   223  	case pcl.IntrinsicConvert:
   224  		from := expr.Args[0]
   225  		to := pcl.LowerConversion(from, expr.Signature.ReturnType)
   226  		output, isOutput := to.(*model.OutputType)
   227  		if isOutput {
   228  			to = output.ElementType
   229  		}
   230  		switch to := to.(type) {
   231  		case *model.EnumType:
   232  			components := strings.Split(to.Token, ":")
   233  			contract.Assertf(len(components) == 3, "malformed token %v", to.Token)
   234  			enum, ok := pcl.GetSchemaForType(to)
   235  			if !ok {
   236  				// No schema was provided
   237  				g.Fgenf(w, "%.v", expr.Args[0])
   238  				return
   239  			}
   240  			moduleNameOverrides := enum.(*schema.EnumType).Package.Language["python"].(PackageInfo).ModuleNameOverrides
   241  			pkg := strings.ReplaceAll(components[0], "-", "_")
   242  			if m := tokenToModule(to.Token, &schema.Package{}, moduleNameOverrides); m != "" {
   243  				pkg += "." + m
   244  			}
   245  			enumName := tokenToName(to.Token)
   246  
   247  			if isOutput {
   248  				g.Fgenf(w, "%.v.apply(lambda x: %s.%s(x))", from, pkg, enumName)
   249  			} else {
   250  				pcl.GenEnum(to, from, func(member *schema.Enum) {
   251  					tag := member.Name
   252  					if tag == "" {
   253  						tag = fmt.Sprintf("%v", member.Value)
   254  					}
   255  					tag, err := makeSafeEnumName(tag, enumName)
   256  					contract.AssertNoError(err)
   257  					g.Fgenf(w, "%s.%s.%s", pkg, enumName, tag)
   258  				}, func(from model.Expression) {
   259  					g.Fgenf(w, "%s.%s(%.v)", pkg, enumName, from)
   260  				})
   261  			}
   262  		default:
   263  			switch arg := from.(type) {
   264  			case *model.ObjectConsExpression:
   265  				g.genObjectConsExpression(w, arg, expr.Type())
   266  			default:
   267  				g.Fgenf(w, "%.v", expr.Args[0])
   268  			}
   269  
   270  		}
   271  	case pcl.IntrinsicApply:
   272  		g.genApply(w, expr)
   273  	case "element":
   274  		g.Fgenf(w, "%.16v[%.v]", expr.Args[0], expr.Args[1])
   275  	case "entries":
   276  		g.Fgenf(w, `[{"key": k, "value": v} for k, v in %.v]`, expr.Args[0])
   277  	case "fileArchive":
   278  		g.Fgenf(w, "pulumi.FileArchive(%.v)", expr.Args[0])
   279  	case "remoteArchive":
   280  		g.Fgenf(w, "pulumi.RemoteArchive(%.v)", expr.Args[0])
   281  	case "assetArchive":
   282  		g.Fgenf(w, "pulumi.AssetArchive(%.v)", expr.Args[0])
   283  	case "fileAsset":
   284  		g.Fgenf(w, "pulumi.FileAsset(%.v)", expr.Args[0])
   285  	case "stringAsset":
   286  		g.Fgenf(w, "pulumi.StringAsset(%.v)", expr.Args[0])
   287  	case "remoteAsset":
   288  		g.Fgenf(w, "pulumi.remoteAsset(%.v)", expr.Args[0])
   289  	case "filebase64":
   290  		g.Fgenf(w, "(lambda path: base64.b64encode(open(path).read().encode()).decode())(%.v)", expr.Args[0])
   291  	case "filebase64sha256":
   292  		// Assuming the existence of the following helper method
   293  		g.Fgenf(w, "computeFilebase64sha256(%v)", expr.Args[0])
   294  	case pcl.Invoke:
   295  		pkg, module, fn, diags := functionName(expr.Args[0])
   296  		contract.Assert(len(diags) == 0)
   297  		if module != "" {
   298  			module = "." + module
   299  		}
   300  		name := fmt.Sprintf("%s%s.%s", pkg, module, PyName(fn))
   301  
   302  		isOut := pcl.IsOutputVersionInvokeCall(expr)
   303  		if isOut {
   304  			name = fmt.Sprintf("%s_output", name)
   305  		}
   306  
   307  		if len(expr.Args) == 1 {
   308  			g.Fprintf(w, "%s()", name)
   309  			return
   310  		}
   311  
   312  		optionsBag := ""
   313  		if len(expr.Args) == 3 {
   314  			var buf bytes.Buffer
   315  			g.Fgenf(&buf, ", %.v", expr.Args[2])
   316  			optionsBag = buf.String()
   317  		}
   318  
   319  		g.Fgenf(w, "%s(", name)
   320  
   321  		genFuncArgs := func(objectExpr *model.ObjectConsExpression) {
   322  			indenter := func(f func()) { f() }
   323  			if len(objectExpr.Items) > 1 {
   324  				indenter = g.Indented
   325  			}
   326  			indenter(func() {
   327  				for i, item := range objectExpr.Items {
   328  					// Ignore non-literal keys
   329  					key, ok := item.Key.(*model.LiteralValueExpression)
   330  					if !ok || !key.Value.Type().Equals(cty.String) {
   331  						continue
   332  					}
   333  					keyVal := PyName(key.Value.AsString())
   334  					if i == 0 {
   335  						g.Fgenf(w, "%s=%.v", keyVal, item.Value)
   336  					} else {
   337  						g.Fgenf(w, ",\n%s%s=%.v", g.Indent, keyVal, item.Value)
   338  					}
   339  				}
   340  			})
   341  		}
   342  
   343  		switch arg := expr.Args[1].(type) {
   344  		case *model.FunctionCallExpression:
   345  			if argsObject, ok := arg.Args[0].(*model.ObjectConsExpression); ok {
   346  				genFuncArgs(argsObject)
   347  			}
   348  
   349  		case *model.ObjectConsExpression:
   350  			genFuncArgs(arg)
   351  		}
   352  
   353  		g.Fgenf(w, "%v)", optionsBag)
   354  	case "join":
   355  		g.Fgenf(w, "%.16v.join(%v)", expr.Args[0], expr.Args[1])
   356  	case "length":
   357  		g.Fgenf(w, "len(%.v)", expr.Args[0])
   358  	case "lookup":
   359  		if len(expr.Args) == 3 {
   360  			g.Fgenf(w, "(lambda v, def: v if v is not None else def)(%.16v[%.v], %.v)",
   361  				expr.Args[0], expr.Args[1], expr.Args[2])
   362  		} else {
   363  			g.Fgenf(w, "%.16v[%.v]", expr.Args[0], expr.Args[1])
   364  		}
   365  	case "range":
   366  		g.Fprint(w, "range(")
   367  		for i, arg := range expr.Args {
   368  			if i > 0 {
   369  				g.Fprint(w, ", ")
   370  			}
   371  			g.Fgenf(w, "%.v", arg)
   372  		}
   373  		g.Fprint(w, ")")
   374  	case "readFile":
   375  		g.Fgenf(w, "(lambda path: open(path).read())(%.v)", expr.Args[0])
   376  	case "readDir":
   377  		g.Fgenf(w, "os.listdir(%.v)", expr.Args[0])
   378  	case "secret":
   379  		g.Fgenf(w, "pulumi.secret(%v)", expr.Args[0])
   380  	case "split":
   381  		g.Fgenf(w, "%.16v.split(%.v)", expr.Args[1], expr.Args[0])
   382  	case "toBase64":
   383  		g.Fgenf(w, "base64.b64encode(%.16v.encode()).decode()", expr.Args[0])
   384  	case "fromBase64":
   385  		g.Fgenf(w, "base64.b64decode(%.16v.encode()).decode()", expr.Args[0])
   386  	case "toJSON":
   387  		g.Fgenf(w, "json.dumps(%.v)", expr.Args[0])
   388  	case "sha1":
   389  		g.Fgenf(w, "hashlib.sha1(%v.encode()).hexdigest()", expr.Args[0])
   390  	case "project":
   391  		g.Fgen(w, "pulumi.get_project()")
   392  	case "stack":
   393  		g.Fgen(w, "pulumi.get_stack()")
   394  	case "cwd":
   395  		g.Fgen(w, "os.getcwd()")
   396  	default:
   397  		var rng hcl.Range
   398  		if expr.Syntax != nil {
   399  			rng = expr.Syntax.Range()
   400  		}
   401  		g.genNYI(w, "FunctionCallExpression: %v (%v)", expr.Name, rng)
   402  	}
   403  }
   404  
   405  func (g *generator) GenIndexExpression(w io.Writer, expr *model.IndexExpression) {
   406  	g.Fgenf(w, "%.16v[%.v]", expr.Collection, expr.Key)
   407  }
   408  
   409  type runeWriter interface {
   410  	WriteRune(c rune) (int, error)
   411  }
   412  
   413  // nolint: errcheck
   414  func (g *generator) genEscapedString(w runeWriter, v string, escapeNewlines, escapeBraces bool) {
   415  	for _, c := range v {
   416  		switch c {
   417  		case '\n':
   418  			if escapeNewlines {
   419  				w.WriteRune('\\')
   420  				c = 'n'
   421  			}
   422  		case '"', '\\':
   423  			if escapeNewlines {
   424  				w.WriteRune('\\')
   425  			}
   426  		case '{', '}':
   427  			if escapeBraces {
   428  				w.WriteRune(c)
   429  			}
   430  		}
   431  		w.WriteRune(c)
   432  	}
   433  }
   434  
   435  func (g *generator) genStringLiteral(w io.Writer, quotes, v string) {
   436  	builder := &strings.Builder{}
   437  
   438  	builder.WriteString(quotes)
   439  	escapeNewlines := quotes == `"` || quotes == `'`
   440  	g.genEscapedString(builder, v, escapeNewlines, false)
   441  	builder.WriteString(quotes)
   442  
   443  	g.Fgenf(w, "%s", builder.String())
   444  }
   445  
   446  func (g *generator) GenLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression) {
   447  	typ := expr.Type()
   448  	if cns, ok := typ.(*model.ConstType); ok {
   449  		typ = cns.Type
   450  	}
   451  
   452  	switch typ {
   453  	case model.BoolType:
   454  		if expr.Value.True() {
   455  			g.Fgen(w, "True")
   456  		} else {
   457  			g.Fgen(w, "False")
   458  		}
   459  	case model.NoneType:
   460  		g.Fgen(w, "None")
   461  	case model.NumberType:
   462  		bf := expr.Value.AsBigFloat()
   463  		if i, acc := bf.Int64(); acc == big.Exact {
   464  			g.Fgenf(w, "%d", i)
   465  		} else {
   466  			f, _ := bf.Float64()
   467  			g.Fgenf(w, "%g", f)
   468  		}
   469  	case model.StringType:
   470  		quotes := g.quotes[expr]
   471  		g.genStringLiteral(w, quotes, expr.Value.AsString())
   472  	default:
   473  		contract.Failf("unexpected literal type in GenLiteralValueExpression: %v (%v)", expr.Type(),
   474  			expr.SyntaxNode().Range())
   475  	}
   476  }
   477  
   478  func (g *generator) GenObjectConsExpression(w io.Writer, expr *model.ObjectConsExpression) {
   479  	g.genObjectConsExpression(w, expr, expr.Type())
   480  }
   481  
   482  func (g *generator) genObjectConsExpression(w io.Writer, expr *model.ObjectConsExpression, destType model.Type) {
   483  	typeName := g.argumentTypeName(expr, destType) // Example: aws.s3.BucketLoggingArgs
   484  	if typeName != "" {
   485  		// If a typeName exists, treat this as an Input Class e.g. aws.s3.BucketLoggingArgs(key="value", foo="bar", ...)
   486  		if len(expr.Items) == 0 {
   487  			g.Fgenf(w, "%s()", typeName)
   488  		} else {
   489  			g.Fgenf(w, "%s(\n", typeName)
   490  			g.Indented(func() {
   491  				for _, item := range expr.Items {
   492  					g.Fgenf(w, "%s", g.Indent)
   493  					lit := item.Key.(*model.LiteralValueExpression)
   494  					g.Fprint(w, PyName(lit.Value.AsString()))
   495  					g.Fgenf(w, "=%.v,\n", item.Value)
   496  				}
   497  			})
   498  			g.Fgenf(w, "%s)", g.Indent)
   499  		}
   500  	} else {
   501  		// Otherwise treat this as an untyped dictionary e.g. {"key": "value", "foo": "bar", ...}
   502  		if len(expr.Items) == 0 {
   503  			g.Fgen(w, "{}")
   504  		} else {
   505  			g.Fgen(w, "{")
   506  			g.Indented(func() {
   507  				for _, item := range expr.Items {
   508  					g.Fgenf(w, "\n%s%.v: %.v,", g.Indent, item.Key, item.Value)
   509  				}
   510  			})
   511  			g.Fgenf(w, "\n%s}", g.Indent)
   512  		}
   513  	}
   514  }
   515  
   516  func (g *generator) genRelativeTraversal(w io.Writer, traversal hcl.Traversal, parts []model.Traversable) {
   517  	for _, traverser := range traversal {
   518  		var key cty.Value
   519  		switch traverser := traverser.(type) {
   520  		case hcl.TraverseAttr:
   521  			key = cty.StringVal(traverser.Name)
   522  		case hcl.TraverseIndex:
   523  			key = traverser.Key
   524  		default:
   525  			contract.Failf("unexpected traverser of type %T (%v)", traverser, traverser.SourceRange())
   526  		}
   527  
   528  		switch key.Type() {
   529  		case cty.String:
   530  			keyVal := key.AsString()
   531  			contract.Assert(isLegalIdentifier(keyVal))
   532  			g.Fgenf(w, ".%s", keyVal)
   533  		case cty.Number:
   534  			idx, _ := key.AsBigFloat().Int64()
   535  			g.Fgenf(w, "[%d]", idx)
   536  		default:
   537  			keyExpr := &model.LiteralValueExpression{Value: key}
   538  			diags := keyExpr.Typecheck(false)
   539  			contract.Ignore(diags)
   540  
   541  			g.Fgenf(w, "[%v]", keyExpr)
   542  		}
   543  	}
   544  }
   545  
   546  func (g *generator) GenRelativeTraversalExpression(w io.Writer, expr *model.RelativeTraversalExpression) {
   547  	g.Fgenf(w, "%.16v", expr.Source)
   548  	g.genRelativeTraversal(w, expr.Traversal, expr.Parts)
   549  }
   550  
   551  func (g *generator) GenScopeTraversalExpression(w io.Writer, expr *model.ScopeTraversalExpression) {
   552  	rootName := PyName(expr.RootName)
   553  	if _, ok := expr.Parts[0].(*model.SplatVariable); ok {
   554  		rootName = "__item"
   555  	}
   556  
   557  	g.Fgen(w, rootName)
   558  	g.genRelativeTraversal(w, expr.Traversal.SimpleSplit().Rel, expr.Parts)
   559  }
   560  
   561  func (g *generator) GenSplatExpression(w io.Writer, expr *model.SplatExpression) {
   562  	g.Fgenf(w, "[%.v for __item in %.v]", expr.Each, expr.Source)
   563  }
   564  
   565  func (g *generator) GenTemplateExpression(w io.Writer, expr *model.TemplateExpression) {
   566  	quotes := g.quotes[expr]
   567  	escapeNewlines := quotes == `"` || quotes == `'`
   568  
   569  	prefix, escapeBraces := "", false
   570  	for _, part := range expr.Parts {
   571  		if lit, ok := part.(*model.LiteralValueExpression); !ok || !model.StringType.AssignableFrom(lit.Type()) {
   572  			prefix, escapeBraces = "f", true
   573  			break
   574  		}
   575  	}
   576  
   577  	b := bufio.NewWriter(w)
   578  	defer b.Flush()
   579  
   580  	g.Fprintf(b, "%s%s", prefix, quotes)
   581  	for _, expr := range expr.Parts {
   582  		if lit, ok := expr.(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
   583  			g.genEscapedString(b, lit.Value.AsString(), escapeNewlines, escapeBraces)
   584  		} else {
   585  			g.Fgenf(b, "{%.v}", expr)
   586  		}
   587  	}
   588  	g.Fprint(b, quotes)
   589  }
   590  
   591  func (g *generator) GenTemplateJoinExpression(w io.Writer, expr *model.TemplateJoinExpression) {
   592  	g.genNYI(w, "TemplateJoinExpression")
   593  }
   594  
   595  func (g *generator) GenTupleConsExpression(w io.Writer, expr *model.TupleConsExpression) {
   596  	switch len(expr.Expressions) {
   597  	case 0:
   598  		g.Fgen(w, "[]")
   599  	case 1:
   600  		g.Fgenf(w, "[%.v]", expr.Expressions[0])
   601  	default:
   602  		g.Fgen(w, "[")
   603  		g.Indented(func() {
   604  			for _, v := range expr.Expressions {
   605  				g.Fgenf(w, "\n%s%.v,", g.Indent, v)
   606  			}
   607  		})
   608  		g.Fgen(w, "\n", g.Indent, "]")
   609  	}
   610  }
   611  
   612  func (g *generator) GenUnaryOpExpression(w io.Writer, expr *model.UnaryOpExpression) {
   613  	opstr, precedence := "", g.GetPrecedence(expr)
   614  	switch expr.Operation {
   615  	case hclsyntax.OpLogicalNot:
   616  		opstr = "not "
   617  	case hclsyntax.OpNegate:
   618  		opstr = "-"
   619  	}
   620  	g.Fgenf(w, "%[2]v%.[1]*[3]v", precedence, opstr, expr.Operand)
   621  }