fdd68833f3
Rather than setting an "Unwrap" field on the existing TemplateExpr, we'll instead use a separate, simpler AST node. There is very little in common between these two cases, so overloading the TemplateExpr node doesn't buy much. A separate node is needed here, rather than just returning the wrapped node directly, to give us somewhere to capture the full source range extent of the wrapping template, whereas the inner expression only captures the range of itself. This is important both for good diagnostics and for transforming zclsyntax AST into zclwrite AST.
113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
package zclsyntax
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
"github.com/zclconf/go-zcl/zcl"
|
|
)
|
|
|
|
type TemplateExpr struct {
|
|
Parts []Expression
|
|
|
|
SrcRange zcl.Range
|
|
}
|
|
|
|
func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) {
|
|
for i, part := range e.Parts {
|
|
e.Parts[i] = w(part).(Expression)
|
|
}
|
|
}
|
|
|
|
func (e *TemplateExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
buf := &bytes.Buffer{}
|
|
var diags zcl.Diagnostics
|
|
isKnown := true
|
|
|
|
for _, part := range e.Parts {
|
|
partVal, partDiags := part.Value(ctx)
|
|
diags = append(diags, partDiags...)
|
|
|
|
if partVal.IsNull() {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Invalid template interpolation value",
|
|
Detail: fmt.Sprintf(
|
|
"The expression result is null. Cannot include a null value in a string template.",
|
|
),
|
|
Subject: part.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
continue
|
|
}
|
|
|
|
if !partVal.IsKnown() {
|
|
// If any part is unknown then the result as a whole must be
|
|
// unknown too. We'll keep on processing the rest of the parts
|
|
// anyway, because we want to still emit any diagnostics resulting
|
|
// from evaluating those.
|
|
isKnown = false
|
|
continue
|
|
}
|
|
|
|
strVal, err := convert.Convert(partVal, cty.String)
|
|
if err != nil {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Invalid template interpolation value",
|
|
Detail: fmt.Sprintf(
|
|
"Cannot include the given value in a string template: %s.",
|
|
err.Error(),
|
|
),
|
|
Subject: part.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
continue
|
|
}
|
|
|
|
buf.WriteString(strVal.AsString())
|
|
}
|
|
|
|
if !isKnown {
|
|
return cty.UnknownVal(cty.String), diags
|
|
}
|
|
|
|
return cty.StringVal(buf.String()), diags
|
|
}
|
|
|
|
func (e *TemplateExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *TemplateExpr) StartRange() zcl.Range {
|
|
return e.Parts[0].StartRange()
|
|
}
|
|
|
|
// TemplateWrapExpr is used instead of a TemplateExpr when a template
|
|
// consists _only_ of a single interpolation sequence. In that case, the
|
|
// template's result is the single interpolation's result, verbatim with
|
|
// no type conversions.
|
|
type TemplateWrapExpr struct {
|
|
Wrapped Expression
|
|
|
|
SrcRange zcl.Range
|
|
}
|
|
|
|
func (e *TemplateWrapExpr) walkChildNodes(w internalWalkFunc) {
|
|
e.Wrapped = w(e.Wrapped).(Expression)
|
|
}
|
|
|
|
func (e *TemplateWrapExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
return e.Wrapped.Value(ctx)
|
|
}
|
|
|
|
func (e *TemplateWrapExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *TemplateWrapExpr) StartRange() zcl.Range {
|
|
return e.SrcRange
|
|
}
|