node: implement heredocs + string interpolation

This commit is contained in:
mappu 2020-04-12 14:40:19 +12:00
parent 5ba0185bcb
commit df0061041f

41
node.go
View File

@ -1465,6 +1465,19 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
return quoteGoString(rawValue), nil // Go source code quoting format return quoteGoString(rawValue), nil // Go source code quoting format
case *scalar.EncapsedStringPart:
// The n.Value that we receive here does not contain quotes at all
// TODO what happens with embedded escape characters? Did the parser
// remove them for us already or do we just have to assume we are in
// a double-quoted-equivalent situation?
return quoteGoString(n.Value), nil // Go source code quoting format
case *scalar.Encapsed:
return this.convertEncapsedString(n.Parts)
case *scalar.Heredoc:
return this.convertEncapsedString(n.Parts)
case *scalar.MagicConstant: case *scalar.MagicConstant:
// magic constants are case-insensitive // magic constants are case-insensitive
switch strings.ToLower(n.Value) { switch strings.ToLower(n.Value) {
@ -1599,6 +1612,13 @@ func removeParens(expr string) string {
return expr return expr
} }
// asSingleExpression makes the expr into a single expression that is safe for
// embedding in a larger statement e.g. multiple string concatenation.
func asSingleExpression(expr string) string {
return `(` + removeParens(expr) + `)`
// TODO simplify this in cases where we can tell it is already a single expression e.g. quoted strings, number literals, variable literals, ...
}
func quoteGoString(s string) string { func quoteGoString(s string) string {
if !strings.Contains(s, "`") && strings.Count(s, "\n") >= 3 { // TODO make the heuristic configurable if !strings.Contains(s, "`") && strings.Count(s, "\n") >= 3 { // TODO make the heuristic configurable
@ -1611,6 +1631,27 @@ func quoteGoString(s string) string {
} }
func (this *conversionState) convertEncapsedString(parts []node.Node) (string, error) { func (this *conversionState) convertEncapsedString(parts []node.Node) (string, error) {
// This is used for string interpolation: `echo "bar $ref";`
// And also for heredocs
// Child nodes are either EncapsedStringPart or expr.**
ret := make([]string, 0, len(parts))
for _, part := range parts {
pp, err := this.convert(part)
if err != nil {
return "", parseErr{part, err}
}
if _, ok := part.(*scalar.EncapsedStringPart); !ok {
// Subexpression - should probably parenthesise just in case
pp = asSingleExpression(pp)
}
ret = append(ret, pp)
}
return `(` + strings.Join(ret, ` + `) + `)`, nil
}
// resolveName turns a `*name.Name` node into a Go string. // resolveName turns a `*name.Name` node into a Go string.
func (this *conversionState) resolveName(n node.Node) (string, error) { func (this *conversionState) resolveName(n node.Node) (string, error) {
// TODO support namespace lookups // TODO support namespace lookups