zclsyntax: template single-interpolation case as separate AST node

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.
This commit is contained in:
Martin Atkins 2017-06-18 07:44:57 -07:00
parent e594a232b3
commit fdd68833f3
5 changed files with 59 additions and 24 deletions

View File

@ -11,7 +11,6 @@ import (
type TemplateExpr struct {
Parts []Expression
Unwrap bool
SrcRange zcl.Range
}
@ -23,14 +22,6 @@ func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) {
}
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{}
var diags zcl.Diagnostics
isKnown := true
@ -93,3 +84,29 @@ func (e *TemplateExpr) Range() zcl.Range {
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
}

View File

@ -55,6 +55,10 @@ func (e *TemplateExpr) Variables() []zcl.Traversal {
return Variables(e)
}
func (e *TemplateWrapExpr) Variables() []zcl.Traversal {
return Variables(e)
}
func (e *TupleConsExpr) Variables() []zcl.Traversal {
return Variables(e)
}

View File

@ -761,14 +761,22 @@ func (p *parser) parseExpressionTerm() (Expression, zcl.Diagnostics) {
case TokenOQuote, TokenOHeredoc:
open := p.Read() // eat opening marker
closer := p.oppositeBracket(open.Type)
exprs, unwrap, _, diags := p.parseTemplateInner(closer)
exprs, passthru, _, diags := p.parseTemplateInner(closer)
closeRange := p.PrevRange()
if passthru {
if len(exprs) != 1 {
panic("passthru set with len(exprs) != 1")
}
return &TemplateWrapExpr{
Wrapped: exprs[0],
SrcRange: zcl.RangeBetween(open.Range, closeRange),
}, diags
}
return &TemplateExpr{
Parts: exprs,
Unwrap: unwrap,
SrcRange: zcl.RangeBetween(open.Range, closeRange),
}, diags

View File

@ -14,12 +14,20 @@ func (p *parser) ParseTemplate() (Expression, zcl.Diagnostics) {
}
func (p *parser) parseTemplate(end TokenType) (Expression, zcl.Diagnostics) {
exprs, unwrap, rng, diags := p.parseTemplateInner(end)
exprs, passthru, rng, diags := p.parseTemplateInner(end)
if passthru {
if len(exprs) != 1 {
panic("passthru set with len(exprs) != 1")
}
return &TemplateWrapExpr{
Wrapped: exprs[0],
SrcRange: rng,
}, diags
}
return &TemplateExpr{
Parts: exprs,
Unwrap: unwrap,
SrcRange: rng,
}, diags
}
@ -33,14 +41,14 @@ func (p *parser) parseTemplateInner(end TokenType) ([]Expression, bool, zcl.Rang
exprs, exprsDiags := tp.parseRoot()
diags = append(diags, exprsDiags...)
unwrap := false
passthru := false
if len(parts.Tokens) == 2 { // one real token and one synthetic "end" token
if _, isInterp := parts.Tokens[0].(*templateInterpToken); isInterp {
unwrap = true
passthru = true
}
}
return exprs, unwrap, parts.SrcRange, diags
return exprs, passthru, parts.SrcRange, diags
}
type templateParser struct {
@ -87,8 +95,7 @@ func (p *templateParser) parseExpr() (Expression, zcl.Diagnostics) {
return p.parseIf()
case *templateForToken:
// TODO: implement
panic("template for token not yet implemented")
return p.parseFor()
case *templateEndToken:
p.Read() // eat erroneous token

View File

@ -433,7 +433,6 @@ block "valid" {}
},
},
},
Unwrap: false,
SrcRange: zcl.Range{
Start: zcl.Pos{Line: 1, Column: 5, Byte: 4},