zclsyntax: deal with template unwrapping at eval time
Previously we were detecting the exactly-one-part case at parse time and skipping the TemplateExpr node in the AST, but this was problematic because we then misaligned the source range for the expression to the _content_ of the quotes, rather than including the quotes themselves. As well as producing confusing diagnostics, this also caused problems for zclwrite since it relies on source ranges to map our AST back onto the source tokens it came from.
This commit is contained in:
parent
e571ec5810
commit
09f9e6c8e8
@ -10,7 +10,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TemplateExpr struct {
|
type TemplateExpr struct {
|
||||||
Parts []Expression
|
Parts []Expression
|
||||||
|
Unwrap bool
|
||||||
|
|
||||||
SrcRange zcl.Range
|
SrcRange zcl.Range
|
||||||
}
|
}
|
||||||
@ -22,6 +23,14 @@ func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *TemplateExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
func (e *TemplateExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||||||
|
if e.Unwrap {
|
||||||
|
if len(e.Parts) != 1 {
|
||||||
|
// should never happen - parser bug, if so
|
||||||
|
panic("Unwrap set with len(e.Parts) != 1")
|
||||||
|
}
|
||||||
|
return e.Parts[0].Value(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
var diags zcl.Diagnostics
|
var diags zcl.Diagnostics
|
||||||
isKnown := true
|
isKnown := true
|
||||||
|
@ -698,7 +698,16 @@ func (p *parser) parseExpressionTerm() (Expression, zcl.Diagnostics) {
|
|||||||
case TokenOQuote, TokenOHeredoc:
|
case TokenOQuote, TokenOHeredoc:
|
||||||
open := p.Read() // eat opening marker
|
open := p.Read() // eat opening marker
|
||||||
closer := p.oppositeBracket(open.Type)
|
closer := p.oppositeBracket(open.Type)
|
||||||
return p.ParseTemplate(closer)
|
parts, unwrap, diags := p.parseTemplateParts(closer)
|
||||||
|
|
||||||
|
closeRange := p.PrevRange()
|
||||||
|
|
||||||
|
return &TemplateExpr{
|
||||||
|
Parts: parts,
|
||||||
|
Unwrap: unwrap,
|
||||||
|
|
||||||
|
SrcRange: zcl.RangeBetween(open.Range, closeRange),
|
||||||
|
}, diags
|
||||||
|
|
||||||
case TokenMinus:
|
case TokenMinus:
|
||||||
tok := p.Read() // eat minus token
|
tok := p.Read() // eat minus token
|
||||||
@ -1028,7 +1037,27 @@ func (p *parser) parseObjectCons() (Expression, zcl.Diagnostics) {
|
|||||||
}, diags
|
}, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) ParseTemplate(end TokenType) (Expression, zcl.Diagnostics) {
|
func (p *parser) ParseTemplate() (Expression, zcl.Diagnostics) {
|
||||||
|
startRange := p.NextRange()
|
||||||
|
parts, unwrap, diags := p.parseTemplateParts(TokenEOF)
|
||||||
|
endRange := p.PrevRange()
|
||||||
|
|
||||||
|
return &TemplateExpr{
|
||||||
|
Parts: parts,
|
||||||
|
Unwrap: unwrap,
|
||||||
|
|
||||||
|
SrcRange: zcl.RangeBetween(startRange, endRange),
|
||||||
|
}, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTemplateParts parses the expressions that make up the content of a
|
||||||
|
// template, up to the given closing delimiter. It also returns a flag that
|
||||||
|
// is true if the first part should be returned as-is, or false if the
|
||||||
|
// full set of parts should be wrapped in a TemplateExpr to return.
|
||||||
|
//
|
||||||
|
// The wrapping is done separately by the caller so that any template
|
||||||
|
// delimiters can be included in the template's source range.
|
||||||
|
func (p *parser) parseTemplateParts(end TokenType) ([]Expression, bool, zcl.Diagnostics) {
|
||||||
var parts []Expression
|
var parts []Expression
|
||||||
var diags zcl.Diagnostics
|
var diags zcl.Diagnostics
|
||||||
|
|
||||||
@ -1129,29 +1158,19 @@ Token:
|
|||||||
// If a sequence has no content, we'll treat it as if it had an
|
// If a sequence has no content, we'll treat it as if it had an
|
||||||
// empty string in it because that's what the user probably means
|
// empty string in it because that's what the user probably means
|
||||||
// if they write "" in configuration.
|
// if they write "" in configuration.
|
||||||
return &LiteralValueExpr{
|
return []Expression{
|
||||||
Val: cty.StringVal(""),
|
&LiteralValueExpr{
|
||||||
SrcRange: zcl.Range{
|
Val: cty.StringVal(""),
|
||||||
Filename: startRange.Filename,
|
SrcRange: zcl.Range{
|
||||||
Start: startRange.Start,
|
Filename: startRange.Filename,
|
||||||
End: startRange.Start,
|
Start: startRange.Start,
|
||||||
|
End: startRange.Start,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, diags
|
}, true, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(parts) == 1 {
|
return parts, len(parts) == 1, diags
|
||||||
// If a sequence only has one part then as a special case we return
|
|
||||||
// that part alone. This allows the use of single-part templates to
|
|
||||||
// represent general expressions in syntaxes such as JSON where
|
|
||||||
// un-quoted expressions are not possible.
|
|
||||||
return parts[0], diags
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TemplateExpr{
|
|
||||||
Parts: parts,
|
|
||||||
|
|
||||||
SrcRange: zcl.RangeBetween(parts[0].Range(), parts[len(parts)-1].Range()),
|
|
||||||
}, diags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseQuotedStringLiteral is a helper for parsing quoted strings that
|
// parseQuotedStringLiteral is a helper for parsing quoted strings that
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/kylelemons/godebug/pretty"
|
"github.com/kylelemons/godebug/pretty"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-zcl/zcl"
|
"github.com/zclconf/go-zcl/zcl"
|
||||||
@ -406,6 +407,65 @@ block "valid" {}
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"a = \"hello ${true}\"\n",
|
||||||
|
0,
|
||||||
|
&Body{
|
||||||
|
Attributes: Attributes{
|
||||||
|
"a": {
|
||||||
|
Name: "a",
|
||||||
|
Expr: &TemplateExpr{
|
||||||
|
Parts: []Expression{
|
||||||
|
&LiteralValueExpr{
|
||||||
|
Val: cty.StringVal("hello "),
|
||||||
|
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&LiteralValueExpr{
|
||||||
|
Val: cty.True,
|
||||||
|
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 14, Byte: 13},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 18, Byte: 17},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Unwrap: false,
|
||||||
|
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 20, Byte: 19},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 20, Byte: 19},
|
||||||
|
},
|
||||||
|
NameRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||||
|
},
|
||||||
|
EqualsRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 3, Byte: 2},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 4, Byte: 3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Blocks: Blocks{},
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
End: zcl.Pos{Line: 2, Column: 1, Byte: 20},
|
||||||
|
},
|
||||||
|
EndRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 2, Column: 1, Byte: 20},
|
||||||
|
End: zcl.Pos{Line: 2, Column: 1, Byte: 20},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"a = foo.bar\n",
|
"a = foo.bar\n",
|
||||||
0,
|
0,
|
||||||
@ -560,7 +620,11 @@ block "valid" {}
|
|||||||
|
|
||||||
if !reflect.DeepEqual(got, test.want) {
|
if !reflect.DeepEqual(got, test.want) {
|
||||||
diff := prettyConfig.Compare(test.want, got)
|
diff := prettyConfig.Compare(test.want, got)
|
||||||
t.Errorf("wrong result\ninput: %s\ndiff: %s", test.input, diff)
|
if diff != "" {
|
||||||
|
t.Errorf("wrong result\ninput: %s\ndiff: %s", test.input, diff)
|
||||||
|
} else {
|
||||||
|
t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", test.input, spew.Sdump(got), spew.Sdump(test.want))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ func ParseTemplate(src []byte, filename string, start zcl.Pos) (Expression, zcl.
|
|||||||
tokens, diags := LexTemplate(src, filename, start)
|
tokens, diags := LexTemplate(src, filename, start)
|
||||||
peeker := newPeeker(tokens, false)
|
peeker := newPeeker(tokens, false)
|
||||||
parser := &parser{peeker: peeker}
|
parser := &parser{peeker: peeker}
|
||||||
expr, parseDiags := parser.ParseTemplate(TokenEOF)
|
expr, parseDiags := parser.ParseTemplate()
|
||||||
diags = append(diags, parseDiags...)
|
diags = append(diags, parseDiags...)
|
||||||
return expr, diags
|
return expr, diags
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user