[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
//nolint: goconst
|
2020-04-03 08:29:05 +02:00
|
|
|
package python
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2020-04-07 04:43:16 +02:00
|
|
|
"fmt"
|
2020-04-03 08:29:05 +02:00
|
|
|
"io"
|
|
|
|
"math/big"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
2020-04-07 04:43:16 +02:00
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
2020-04-14 10:30:25 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/model"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
|
2020-04-03 08:29:05 +02:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
)
|
|
|
|
|
|
|
|
type nameInfo int
|
|
|
|
|
2020-04-25 07:04:24 +02:00
|
|
|
func (nameInfo) Format(name string) string {
|
|
|
|
return PyName(name)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
func (g *generator) lowerExpression(expr model.Expression) (model.Expression, []*quoteTemp) {
|
2020-04-03 08:29:05 +02:00
|
|
|
// TODO(pdg): diagnostics
|
|
|
|
|
2020-04-07 04:43:16 +02:00
|
|
|
expr, _ = hcl2.RewriteApplies(expr, nameInfo(0), false)
|
2020-04-03 08:29:05 +02:00
|
|
|
expr, _ = g.lowerProxyApplies(expr)
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
expr, quotes, _ := g.rewriteQuotes(expr)
|
|
|
|
return expr, quotes
|
2020-04-17 01:44:34 +02:00
|
|
|
}
|
2020-04-03 08:29:05 +02:00
|
|
|
|
2020-04-17 01:44:34 +02:00
|
|
|
func (g *generator) GetPrecedence(expr model.Expression) int {
|
|
|
|
// Precedence is taken from https://docs.python.org/3/reference/expressions.html#operator-precedence.
|
|
|
|
switch expr := expr.(type) {
|
|
|
|
case *model.AnonymousFunctionExpression:
|
|
|
|
return 1
|
|
|
|
case *model.ConditionalExpression:
|
|
|
|
return 2
|
|
|
|
case *model.BinaryOpExpression:
|
|
|
|
switch expr.Operation {
|
|
|
|
case hclsyntax.OpLogicalOr:
|
|
|
|
return 3
|
|
|
|
case hclsyntax.OpLogicalAnd:
|
|
|
|
return 4
|
|
|
|
case hclsyntax.OpGreaterThan, hclsyntax.OpGreaterThanOrEqual, hclsyntax.OpLessThan, hclsyntax.OpLessThanOrEqual,
|
|
|
|
hclsyntax.OpEqual, hclsyntax.OpNotEqual:
|
|
|
|
return 6
|
|
|
|
case hclsyntax.OpAdd, hclsyntax.OpSubtract:
|
|
|
|
return 11
|
|
|
|
case hclsyntax.OpMultiply, hclsyntax.OpDivide, hclsyntax.OpModulo:
|
|
|
|
return 12
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected binary expression %v", expr)
|
|
|
|
}
|
|
|
|
case *model.UnaryOpExpression:
|
|
|
|
return 13
|
|
|
|
case *model.FunctionCallExpression, *model.IndexExpression, *model.RelativeTraversalExpression,
|
|
|
|
*model.TemplateJoinExpression:
|
|
|
|
return 16
|
|
|
|
case *model.ForExpression, *model.ObjectConsExpression, *model.SplatExpression, *model.TupleConsExpression:
|
|
|
|
return 17
|
|
|
|
case *model.LiteralValueExpression, *model.ScopeTraversalExpression, *model.TemplateExpression:
|
|
|
|
return 18
|
|
|
|
default:
|
|
|
|
contract.Failf("unexpected expression %v of type %T", expr, expr)
|
|
|
|
}
|
|
|
|
return 0
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenAnonymousFunctionExpression(w io.Writer, expr *model.AnonymousFunctionExpression) {
|
|
|
|
g.Fgen(w, "lambda")
|
|
|
|
for i, p := range expr.Signature.Parameters {
|
|
|
|
if i > 0 {
|
|
|
|
g.Fgen(w, ",")
|
|
|
|
}
|
|
|
|
g.Fgenf(w, " %s", p.Name)
|
|
|
|
}
|
|
|
|
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, ": %.v", expr.Body)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenBinaryOpExpression(w io.Writer, expr *model.BinaryOpExpression) {
|
2020-04-17 01:44:34 +02:00
|
|
|
opstr, precedence := "", g.GetPrecedence(expr)
|
2020-04-07 04:43:16 +02:00
|
|
|
switch expr.Operation {
|
|
|
|
case hclsyntax.OpAdd:
|
|
|
|
opstr = "+"
|
|
|
|
case hclsyntax.OpDivide:
|
|
|
|
opstr = "/"
|
|
|
|
case hclsyntax.OpEqual:
|
|
|
|
opstr = "=="
|
|
|
|
case hclsyntax.OpGreaterThan:
|
|
|
|
opstr = ">"
|
|
|
|
case hclsyntax.OpGreaterThanOrEqual:
|
|
|
|
opstr = ">="
|
|
|
|
case hclsyntax.OpLessThan:
|
|
|
|
opstr = "<"
|
|
|
|
case hclsyntax.OpLessThanOrEqual:
|
|
|
|
opstr = "<="
|
|
|
|
case hclsyntax.OpLogicalAnd:
|
|
|
|
opstr = "and"
|
|
|
|
case hclsyntax.OpLogicalOr:
|
|
|
|
opstr = "or"
|
|
|
|
case hclsyntax.OpModulo:
|
|
|
|
opstr = "%"
|
|
|
|
case hclsyntax.OpMultiply:
|
|
|
|
opstr = "*"
|
|
|
|
case hclsyntax.OpNotEqual:
|
|
|
|
opstr = "!="
|
|
|
|
case hclsyntax.OpSubtract:
|
|
|
|
opstr = "-"
|
2020-04-17 01:44:34 +02:00
|
|
|
default:
|
|
|
|
opstr, precedence = ",", 0
|
2020-04-07 04:43:16 +02:00
|
|
|
}
|
|
|
|
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "%.[1]*[2]v %[3]v %.[1]*[4]o", precedence, expr.LeftOperand, opstr, expr.RightOperand)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenConditionalExpression(w io.Writer, expr *model.ConditionalExpression) {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "%.2v if %.2v else %.2v", expr.TrueResult, expr.Condition, expr.FalseResult)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenForExpression(w io.Writer, expr *model.ForExpression) {
|
2020-04-07 04:43:16 +02:00
|
|
|
close := "]"
|
|
|
|
if expr.Key != nil {
|
|
|
|
// Dictionary comprehension
|
|
|
|
//
|
|
|
|
// TODO(pdg): grouping
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "{%.v: %.v", expr.Key, expr.Value)
|
2020-04-07 04:43:16 +02:00
|
|
|
close = "}"
|
|
|
|
} else {
|
|
|
|
// List comprehension
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "[%.v", expr.Value)
|
2020-04-07 04:43:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if expr.KeyVariable == nil {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, " for %v in %.v", expr.ValueVariable.Name, expr.Collection)
|
2020-04-07 04:43:16 +02:00
|
|
|
} else {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, " for %v, %v in %.v", expr.KeyVariable.Name, expr.ValueVariable.Name, expr.Collection)
|
2020-04-07 04:43:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if expr.Condition != nil {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, " if %.v", expr.Condition)
|
2020-04-07 04:43:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
g.Fprint(w, close)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) genApply(w io.Writer, expr *model.FunctionCallExpression) {
|
|
|
|
// Extract the list of outputs and the continuation expression from the `__apply` arguments.
|
|
|
|
applyArgs, then := hcl2.ParseApplyCall(expr)
|
|
|
|
|
|
|
|
if len(applyArgs) == 1 {
|
|
|
|
// If we only have a single output, just generate a normal `.apply`.
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "%.16v.apply(%.v)", applyArgs[0], then)
|
2020-04-03 08:29:05 +02:00
|
|
|
} else {
|
|
|
|
// Otherwise, generate a call to `pulumi.all([]).apply()`.
|
|
|
|
g.Fgen(w, "pulumi.Output.all(")
|
|
|
|
for i, o := range applyArgs {
|
|
|
|
if i > 0 {
|
|
|
|
g.Fgen(w, ", ")
|
|
|
|
}
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "%.v", o)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, ").apply(%.v)", then)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 04:43:16 +02:00
|
|
|
// functionName computes the NodeJS package, module, and name for the given function token.
|
|
|
|
func functionName(tokenArg model.Expression) (string, string, string, hcl.Diagnostics) {
|
|
|
|
token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString()
|
|
|
|
tokenRange := tokenArg.SyntaxNode().Range()
|
|
|
|
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
// Compute the resource type from the Pulumi type token.
|
2020-04-07 04:43:16 +02:00
|
|
|
pkg, module, member, diagnostics := hcl2.DecomposeToken(token, tokenRange)
|
2020-05-19 10:18:38 +02:00
|
|
|
return makeValidIdentifier(pkg), strings.Replace(module, "/", ".", -1), title(member), diagnostics
|
2020-04-07 04:43:16 +02:00
|
|
|
}
|
|
|
|
|
2020-04-21 19:24:42 +02:00
|
|
|
var functionImports = map[string]string{
|
|
|
|
"fileArchive": "pulumi",
|
|
|
|
"fileAsset": "pulumi",
|
|
|
|
"readDir": "os",
|
|
|
|
"toJSON": "json",
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) getFunctionImports(x *model.FunctionCallExpression) string {
|
2020-05-11 19:21:56 +02:00
|
|
|
if x.Name != hcl2.Invoke {
|
2020-04-21 19:24:42 +02:00
|
|
|
return functionImports[x.Name]
|
|
|
|
}
|
|
|
|
|
|
|
|
pkg, _, _, diags := functionName(x.Args[0])
|
|
|
|
contract.Assert(len(diags) == 0)
|
|
|
|
return "pulumi_" + pkg
|
|
|
|
}
|
|
|
|
|
2020-04-03 08:29:05 +02:00
|
|
|
func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) {
|
|
|
|
switch expr.Name {
|
|
|
|
case hcl2.IntrinsicApply:
|
|
|
|
g.genApply(w, expr)
|
2020-04-17 01:44:34 +02:00
|
|
|
case "element":
|
|
|
|
g.Fgenf(w, "%.16v[%.v]", expr.Args[0], expr.Args[1])
|
2020-04-07 04:43:16 +02:00
|
|
|
case "entries":
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, `[{"key": k, "value": v} for k, v in %.v]`, expr.Args[0])
|
2020-04-03 08:29:05 +02:00
|
|
|
case "fileArchive":
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "pulumi.FileArchive(%.v)", expr.Args[0])
|
2020-04-03 08:29:05 +02:00
|
|
|
case "fileAsset":
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "pulumi.FileAsset(%.v)", expr.Args[0])
|
2020-05-11 19:21:56 +02:00
|
|
|
case hcl2.Invoke:
|
2020-04-07 04:43:16 +02:00
|
|
|
pkg, module, fn, diags := functionName(expr.Args[0])
|
|
|
|
contract.Assert(len(diags) == 0)
|
|
|
|
if module != "" {
|
|
|
|
module = "." + module
|
|
|
|
}
|
|
|
|
name := fmt.Sprintf("%s%s.%s", pkg, module, PyName(fn))
|
|
|
|
|
|
|
|
optionsBag := ""
|
|
|
|
if len(expr.Args) == 3 {
|
|
|
|
var buf bytes.Buffer
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(&buf, ", %.v", expr.Args[2])
|
2020-04-07 04:43:16 +02:00
|
|
|
optionsBag = buf.String()
|
|
|
|
}
|
|
|
|
|
2020-04-21 22:52:24 +02:00
|
|
|
g.Fgenf(w, "%s(", name)
|
|
|
|
|
2020-04-30 23:16:56 +02:00
|
|
|
casingTable := g.casingTables[pkg]
|
2020-04-21 22:52:24 +02:00
|
|
|
if obj, ok := expr.Args[1].(*model.ObjectConsExpression); ok {
|
2020-04-30 23:16:56 +02:00
|
|
|
g.lowerObjectKeys(expr.Args[1], casingTable)
|
2020-04-21 22:52:24 +02:00
|
|
|
|
|
|
|
indenter := func(f func()) { f() }
|
|
|
|
if len(obj.Items) > 1 {
|
|
|
|
indenter = g.Indented
|
|
|
|
}
|
|
|
|
indenter(func() {
|
|
|
|
for i, item := range obj.Items {
|
|
|
|
// Ignore non-literal keys
|
|
|
|
key, ok := item.Key.(*model.LiteralValueExpression)
|
|
|
|
if !ok || !key.Value.Type().Equals(cty.String) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-04-30 23:16:56 +02:00
|
|
|
keyVal := PyName(key.Value.AsString())
|
2020-04-21 22:52:24 +02:00
|
|
|
if i == 0 {
|
|
|
|
g.Fgenf(w, "%s=%.v", keyVal, item.Value)
|
|
|
|
} else {
|
|
|
|
g.Fgenf(w, ",\n%s%s=%.v", g.Indent, keyVal, item.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
g.Fgenf(w, "%v)", optionsBag)
|
2020-04-17 01:44:34 +02:00
|
|
|
case "length":
|
|
|
|
g.Fgenf(w, "len(%.v)", expr.Args[0])
|
|
|
|
case "lookup":
|
|
|
|
if len(expr.Args) == 3 {
|
|
|
|
g.Fgenf(w, "(lambda v, def: v if v is not None else def)(%.16v[%.v], %.v)",
|
|
|
|
expr.Args[0], expr.Args[1], expr.Args[2])
|
|
|
|
} else {
|
|
|
|
g.Fgenf(w, "%.16v[%.v]", expr.Args[0], expr.Args[1])
|
|
|
|
}
|
2020-04-07 04:43:16 +02:00
|
|
|
case "range":
|
|
|
|
g.Fprint(w, "range(")
|
|
|
|
for i, arg := range expr.Args {
|
|
|
|
if i > 0 {
|
|
|
|
g.Fprint(w, ", ")
|
|
|
|
}
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "%.v", arg)
|
2020-04-07 04:43:16 +02:00
|
|
|
}
|
|
|
|
g.Fprint(w, ")")
|
2020-04-17 01:44:34 +02:00
|
|
|
case "readFile":
|
|
|
|
g.Fgenf(w, "(lambda path: open(path).read())(%.v)", expr.Args[0])
|
|
|
|
case "readDir":
|
|
|
|
g.Fgenf(w, "os.listdir(%.v)", expr.Args[0])
|
|
|
|
case "split":
|
|
|
|
g.Fgenf(w, "%.16v.split(%.v)", expr.Args[1], expr.Args[0])
|
2020-04-03 08:29:05 +02:00
|
|
|
case "toJSON":
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "json.dumps(%.v)", expr.Args[0])
|
2020-04-03 08:29:05 +02:00
|
|
|
default:
|
|
|
|
var rng hcl.Range
|
|
|
|
if expr.Syntax != nil {
|
|
|
|
rng = expr.Syntax.Range()
|
|
|
|
}
|
|
|
|
g.genNYI(w, "FunctionCallExpression: %v (%v)", expr.Name, rng)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenIndexExpression(w io.Writer, expr *model.IndexExpression) {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "%.16v[%.v]", expr.Collection, expr.Key)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type runeWriter interface {
|
|
|
|
WriteRune(c rune) (int, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// nolint: errcheck
|
|
|
|
func (g *generator) genEscapedString(w runeWriter, v string, escapeNewlines, escapeBraces bool) {
|
|
|
|
for _, c := range v {
|
|
|
|
switch c {
|
|
|
|
case '\n':
|
|
|
|
if escapeNewlines {
|
|
|
|
w.WriteRune('\\')
|
|
|
|
c = 'n'
|
|
|
|
}
|
|
|
|
case '"', '\\':
|
2020-04-21 18:18:09 +02:00
|
|
|
if escapeNewlines {
|
|
|
|
w.WriteRune('\\')
|
|
|
|
}
|
2020-04-03 08:29:05 +02:00
|
|
|
case '{', '}':
|
|
|
|
if escapeBraces {
|
|
|
|
w.WriteRune(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
w.WriteRune(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
func (g *generator) genStringLiteral(w io.Writer, quotes, v string) {
|
2020-04-03 08:29:05 +02:00
|
|
|
builder := &strings.Builder{}
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
|
|
|
|
builder.WriteString(quotes)
|
|
|
|
escapeNewlines := quotes == `"` || quotes == `'`
|
|
|
|
g.genEscapedString(builder, v, escapeNewlines, false)
|
|
|
|
builder.WriteString(quotes)
|
2020-04-03 08:29:05 +02:00
|
|
|
|
|
|
|
g.Fgenf(w, "%s", builder.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression) {
|
|
|
|
switch expr.Type() {
|
|
|
|
case model.BoolType:
|
|
|
|
if expr.Value.True() {
|
|
|
|
g.Fgen(w, "True")
|
|
|
|
} else {
|
|
|
|
g.Fgen(w, "False")
|
|
|
|
}
|
|
|
|
case model.NumberType:
|
|
|
|
bf := expr.Value.AsBigFloat()
|
|
|
|
if i, acc := bf.Int64(); acc == big.Exact {
|
|
|
|
g.Fgenf(w, "%d", i)
|
|
|
|
} else {
|
|
|
|
f, _ := bf.Float64()
|
|
|
|
g.Fgenf(w, "%g", f)
|
|
|
|
}
|
|
|
|
case model.StringType:
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
quotes := g.quotes[expr]
|
|
|
|
g.genStringLiteral(w, quotes, expr.Value.AsString())
|
2020-04-03 08:29:05 +02:00
|
|
|
default:
|
|
|
|
contract.Failf("unexpected literal type in GenLiteralValueExpression: %v (%v)", expr.Type(),
|
|
|
|
expr.SyntaxNode().Range())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenObjectConsExpression(w io.Writer, expr *model.ObjectConsExpression) {
|
|
|
|
if len(expr.Items) == 0 {
|
|
|
|
g.Fgen(w, "{}")
|
|
|
|
} else {
|
|
|
|
g.Fgen(w, "{")
|
|
|
|
g.Indented(func() {
|
|
|
|
for _, item := range expr.Items {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "\n%s%.v: %.v,", g.Indent, item.Key, item.Value)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
g.Fgenf(w, "\n%s}", g.Indent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-21 18:18:09 +02:00
|
|
|
func (g *generator) genRelativeTraversal(w io.Writer, traversal hcl.Traversal, parts []model.Traversable) {
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
for _, traverser := range traversal {
|
2020-04-03 08:29:05 +02:00
|
|
|
var key cty.Value
|
2020-04-21 18:18:09 +02:00
|
|
|
switch traverser := traverser.(type) {
|
2020-04-03 08:29:05 +02:00
|
|
|
case hcl.TraverseAttr:
|
2020-04-21 18:18:09 +02:00
|
|
|
key = cty.StringVal(traverser.Name)
|
2020-04-03 08:29:05 +02:00
|
|
|
case hcl.TraverseIndex:
|
2020-04-21 18:18:09 +02:00
|
|
|
key = traverser.Key
|
2020-04-03 08:29:05 +02:00
|
|
|
default:
|
2020-04-21 18:18:09 +02:00
|
|
|
contract.Failf("unexpected traverser of type %T (%v)", traverser, traverser.SourceRange())
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
switch key.Type() {
|
|
|
|
case cty.String:
|
|
|
|
keyVal := key.AsString()
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
contract.Assert(isLegalIdentifier(keyVal))
|
|
|
|
g.Fgenf(w, ".%s", keyVal)
|
2020-04-03 08:29:05 +02:00
|
|
|
case cty.Number:
|
|
|
|
idx, _ := key.AsBigFloat().Int64()
|
|
|
|
g.Fgenf(w, "[%d]", idx)
|
|
|
|
default:
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
keyExpr := &model.LiteralValueExpression{Value: key}
|
|
|
|
diags := keyExpr.Typecheck(false)
|
|
|
|
contract.Ignore(diags)
|
|
|
|
|
|
|
|
g.Fgenf(w, "[%v]", keyExpr)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenRelativeTraversalExpression(w io.Writer, expr *model.RelativeTraversalExpression) {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "%.16v", expr.Source)
|
2020-04-07 04:43:16 +02:00
|
|
|
g.genRelativeTraversal(w, expr.Traversal, expr.Parts)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenScopeTraversalExpression(w io.Writer, expr *model.ScopeTraversalExpression) {
|
2020-04-07 04:43:16 +02:00
|
|
|
rootName := PyName(expr.RootName)
|
2020-04-17 01:44:34 +02:00
|
|
|
if _, ok := expr.Parts[0].(*model.SplatVariable); ok {
|
2020-04-07 04:43:16 +02:00
|
|
|
rootName = "__item"
|
|
|
|
}
|
|
|
|
|
|
|
|
g.Fgen(w, rootName)
|
|
|
|
g.genRelativeTraversal(w, expr.Traversal.SimpleSplit().Rel, expr.Parts)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenSplatExpression(w io.Writer, expr *model.SplatExpression) {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "[%.v for __item in %.v]", expr.Each, expr.Source)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenTemplateExpression(w io.Writer, expr *model.TemplateExpression) {
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
quotes := g.quotes[expr]
|
|
|
|
escapeNewlines := quotes == `"` || quotes == `'`
|
2020-04-03 08:29:05 +02:00
|
|
|
|
2020-04-29 15:20:20 +02:00
|
|
|
prefix, escapeBraces := "", false
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
for _, part := range expr.Parts {
|
|
|
|
if lit, ok := part.(*model.LiteralValueExpression); !ok || lit.Type() != model.StringType {
|
2020-04-29 15:20:20 +02:00
|
|
|
prefix, escapeBraces = "f", true
|
[codegen/python] Fix nested quotes. (#4539)
Unlike most languages with interpolated strings, Python's formatted
string literals do not allow the nesting of quotes. For example,
this expression is not legal Python:
f"Foo {"bar"} baz"
If an interpolation requires quotes, those quotes nust differ from the
quotes used by the enclosing literal. We can fix the previous example
by rewriting it with single quotes:
f"Foo {'bar'} baz"
However, this presents a problem if there are more than two levels of
nesting, as Python only has two kinds of quotes (four if the outermost
string uses """ or '''): in this case, the expression becomes
unspellable, and must be assigned to a local that is then used in place
of the original expression. So this:
f"Foo {bar[f'index {baz["qux"]}']} zed"
becomes this:
index = "qux"
f"Foo {bar[f'index {baz[index]}']}"
To put it bluntly, Python code generation reqiures register allocation,
but for quotes. These changes implement exactly that.
These changes also include a fix for traversals that access values that
are dictionaries rather than objects, and must use indexers rather than
attributes.
2020-05-01 01:34:25 +02:00
|
|
|
break
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b := bufio.NewWriter(w)
|
|
|
|
defer b.Flush()
|
|
|
|
|
2020-04-21 18:18:09 +02:00
|
|
|
g.Fprintf(b, "%s%s", prefix, quotes)
|
2020-04-03 08:29:05 +02:00
|
|
|
for _, expr := range expr.Parts {
|
|
|
|
if lit, ok := expr.(*model.LiteralValueExpression); ok && lit.Type() == model.StringType {
|
2020-04-29 15:20:20 +02:00
|
|
|
g.genEscapedString(b, lit.Value.AsString(), escapeNewlines, escapeBraces)
|
2020-04-03 08:29:05 +02:00
|
|
|
} else {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(b, "{%.v}", expr)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
g.Fprint(b, quotes)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenTemplateJoinExpression(w io.Writer, expr *model.TemplateJoinExpression) {
|
|
|
|
g.genNYI(w, "TemplateJoinExpression")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenTupleConsExpression(w io.Writer, expr *model.TupleConsExpression) {
|
|
|
|
switch len(expr.Expressions) {
|
|
|
|
case 0:
|
|
|
|
g.Fgen(w, "[]")
|
|
|
|
case 1:
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "[%.v]", expr.Expressions[0])
|
2020-04-03 08:29:05 +02:00
|
|
|
default:
|
|
|
|
g.Fgen(w, "[")
|
|
|
|
g.Indented(func() {
|
|
|
|
for _, v := range expr.Expressions {
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "\n%s%.v,", g.Indent, v)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
g.Fgen(w, "\n", g.Indent, "]")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *generator) GenUnaryOpExpression(w io.Writer, expr *model.UnaryOpExpression) {
|
2020-04-17 01:44:34 +02:00
|
|
|
opstr, precedence := "", g.GetPrecedence(expr)
|
2020-04-07 04:43:16 +02:00
|
|
|
switch expr.Operation {
|
|
|
|
case hclsyntax.OpLogicalNot:
|
|
|
|
opstr = "not "
|
|
|
|
case hclsyntax.OpNegate:
|
|
|
|
opstr = "-"
|
|
|
|
}
|
2020-04-17 01:44:34 +02:00
|
|
|
g.Fgenf(w, "%[2]v%.[1]*[3]v", precedence, opstr, expr.Operand)
|
2020-04-03 08:29:05 +02:00
|
|
|
}
|