zclsyntax: parsing of template if construct
Since this ultimately just returns a ConditionalExpr, the evaluation is already implemented too.
This commit is contained in:
parent
2f1bfd284c
commit
15e3d80e6c
@ -128,6 +128,49 @@ trim`,
|
||||
cty.StringVal("truetrimtrue"), // trimming is no-op of neighbors aren't literal strings
|
||||
0,
|
||||
},
|
||||
|
||||
{
|
||||
`!{ if true ~} hello !{~ endif }`,
|
||||
nil,
|
||||
cty.StringVal("hello"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`!{ if false ~} hello !{~ endif}`,
|
||||
nil,
|
||||
cty.StringVal(""),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`!{ if true ~} hello !{~ else ~} goodbye !{~ endif }`,
|
||||
nil,
|
||||
cty.StringVal("hello"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`!{ if false ~} hello !{~ else ~} goodbye !{~ endif }`,
|
||||
nil,
|
||||
cty.StringVal("goodbye"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`!{ if true ~} !{~ if false ~} hello !{~ else ~} goodbye !{~ endif ~} !{~ endif }`,
|
||||
nil,
|
||||
cty.StringVal("goodbye"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`!{ if false ~} !{~ if false ~} hello !{~ else ~} goodbye !{~ endif ~} !{~ endif }`,
|
||||
nil,
|
||||
cty.StringVal(""),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`!{ of true ~} hello !{~ endif}`,
|
||||
nil,
|
||||
cty.UnknownVal(cty.String),
|
||||
2, // "of" is not a valid control keyword, and "endif" is therefore also unexpected
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -9,6 +9,9 @@ type Keyword []byte
|
||||
var forKeyword = Keyword([]byte{'f', 'o', 'r'})
|
||||
var inKeyword = Keyword([]byte{'i', 'n'})
|
||||
var ifKeyword = Keyword([]byte{'i', 'f'})
|
||||
var elseKeyword = Keyword([]byte{'e', 'l', 's', 'e'})
|
||||
var endifKeyword = Keyword([]byte{'e', 'n', 'd', 'i', 'f'})
|
||||
var endforKeyword = Keyword([]byte{'e', 'n', 'd', 'f', 'o', 'r'})
|
||||
|
||||
func (kw Keyword) TokenMatches(token Token) bool {
|
||||
if token.Type != TokenIdent {
|
||||
|
@ -69,27 +69,29 @@ func (p *templateParser) parseRoot() ([]Expression, zcl.Diagnostics) {
|
||||
}
|
||||
|
||||
func (p *templateParser) parseExpr() (Expression, zcl.Diagnostics) {
|
||||
next := p.Read()
|
||||
next := p.Peek()
|
||||
switch tok := next.(type) {
|
||||
|
||||
case *templateLiteralToken:
|
||||
p.Read() // eat literal
|
||||
return &LiteralValueExpr{
|
||||
Val: cty.StringVal(tok.Val),
|
||||
SrcRange: tok.SrcRange,
|
||||
}, nil
|
||||
|
||||
case *templateInterpToken:
|
||||
p.Read() // eat interp
|
||||
return tok.Expr, nil
|
||||
|
||||
case *templateIfToken:
|
||||
// TODO: implement
|
||||
panic("template if token not yet implemented")
|
||||
return p.parseIf()
|
||||
|
||||
case *templateForToken:
|
||||
// TODO: implement
|
||||
panic("template for token not yet implemented")
|
||||
|
||||
case *templateEndToken:
|
||||
p.Read() // eat erroneous token
|
||||
return errPlaceholderExpr(tok.SrcRange), zcl.Diagnostics{
|
||||
{
|
||||
// This is a particularly unhelpful diagnostic, so callers
|
||||
@ -103,6 +105,7 @@ func (p *templateParser) parseExpr() (Expression, zcl.Diagnostics) {
|
||||
}
|
||||
|
||||
case *templateEndCtrlToken:
|
||||
p.Read() // eat erroneous token
|
||||
return errPlaceholderExpr(tok.SrcRange), zcl.Diagnostics{
|
||||
{
|
||||
Severity: zcl.DiagError,
|
||||
@ -118,6 +121,118 @@ func (p *templateParser) parseExpr() (Expression, zcl.Diagnostics) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *templateParser) parseIf() (Expression, zcl.Diagnostics) {
|
||||
open := p.Read()
|
||||
openIf, isIf := open.(*templateIfToken)
|
||||
if !isIf {
|
||||
// should never happen if caller is behaving
|
||||
panic("parseIf called with peeker not pointing at if token")
|
||||
}
|
||||
|
||||
var ifExprs, elseExprs []Expression
|
||||
var diags zcl.Diagnostics
|
||||
var endifRange zcl.Range
|
||||
|
||||
currentExprs := &ifExprs
|
||||
Token:
|
||||
for {
|
||||
next := p.Peek()
|
||||
if end, isEnd := next.(*templateEndToken); isEnd {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Unexpected end of template",
|
||||
Detail: fmt.Sprintf(
|
||||
"The if directive at %s is missing its corresponding endif directive.",
|
||||
openIf.SrcRange,
|
||||
),
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
return errPlaceholderExpr(end.SrcRange), diags
|
||||
}
|
||||
if end, isCtrlEnd := next.(*templateEndCtrlToken); isCtrlEnd {
|
||||
p.Read() // eat end directive
|
||||
|
||||
switch end.Type {
|
||||
|
||||
case templateElse:
|
||||
if currentExprs == &ifExprs {
|
||||
currentExprs = &elseExprs
|
||||
continue Token
|
||||
}
|
||||
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Unexpected else directive",
|
||||
Detail: fmt.Sprintf(
|
||||
"Already in the else clause for the if started at %s.",
|
||||
openIf.SrcRange,
|
||||
),
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
|
||||
case templateEndIf:
|
||||
endifRange = end.SrcRange
|
||||
break Token
|
||||
|
||||
default:
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unexpected %s directive", end.Name()),
|
||||
Detail: fmt.Sprintf(
|
||||
"Expecting an endif directive for the if started at %s.",
|
||||
openIf.SrcRange,
|
||||
),
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
}
|
||||
|
||||
return errPlaceholderExpr(end.SrcRange), diags
|
||||
}
|
||||
|
||||
expr, exprDiags := p.parseExpr()
|
||||
diags = append(diags, exprDiags...)
|
||||
*currentExprs = append(*currentExprs, expr)
|
||||
}
|
||||
|
||||
if len(ifExprs) == 0 {
|
||||
ifExprs = append(ifExprs, &LiteralValueExpr{
|
||||
Val: cty.StringVal(""),
|
||||
SrcRange: zcl.Range{
|
||||
Filename: openIf.SrcRange.Filename,
|
||||
Start: openIf.SrcRange.End,
|
||||
End: openIf.SrcRange.End,
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(elseExprs) == 0 {
|
||||
elseExprs = append(elseExprs, &LiteralValueExpr{
|
||||
Val: cty.StringVal(""),
|
||||
SrcRange: zcl.Range{
|
||||
Filename: endifRange.Filename,
|
||||
Start: endifRange.Start,
|
||||
End: endifRange.Start,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
trueExpr := &TemplateExpr{
|
||||
Parts: ifExprs,
|
||||
SrcRange: zcl.RangeBetween(ifExprs[0].Range(), ifExprs[len(ifExprs)-1].Range()),
|
||||
}
|
||||
falseExpr := &TemplateExpr{
|
||||
Parts: elseExprs,
|
||||
SrcRange: zcl.RangeBetween(elseExprs[0].Range(), elseExprs[len(elseExprs)-1].Range()),
|
||||
}
|
||||
|
||||
return &ConditionalExpr{
|
||||
Condition: openIf.CondExpr,
|
||||
TrueResult: trueExpr,
|
||||
FalseResult: falseExpr,
|
||||
|
||||
SrcRange: zcl.RangeBetween(openIf.SrcRange, endifRange),
|
||||
}, diags
|
||||
}
|
||||
|
||||
func (p *templateParser) Peek() templateToken {
|
||||
return p.Tokens[p.pos]
|
||||
}
|
||||
@ -214,8 +329,104 @@ Token:
|
||||
Expr: expr,
|
||||
SrcRange: zcl.RangeBetween(next.Range, close.Range),
|
||||
})
|
||||
|
||||
case TokenTemplateControl:
|
||||
panic("template control sequences not yet supported")
|
||||
// if the opener is !{~ then we want to eat any trailing whitespace
|
||||
// in the preceding literal token, assuming it is indeed a literal
|
||||
// token.
|
||||
if canTrimPrev && len(next.Bytes) == 3 && next.Bytes[2] == '~' && len(parts) > 0 {
|
||||
prevExpr := parts[len(parts)-1]
|
||||
if lexpr, ok := prevExpr.(*templateLiteralToken); ok {
|
||||
lexpr.Val = strings.TrimRightFunc(lexpr.Val, unicode.IsSpace)
|
||||
}
|
||||
}
|
||||
p.PushIncludeNewlines(false)
|
||||
|
||||
kw := p.Peek()
|
||||
if kw.Type != TokenIdent {
|
||||
if !p.recovery {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Invalid template control keyword",
|
||||
Detail: "A template control keyword (\"if\", \"for\", etc) is expected at the beginning of a !{ sequence.",
|
||||
Subject: &kw.Range,
|
||||
Context: zcl.RangeBetween(next.Range, kw.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
p.PopIncludeNewlines()
|
||||
continue Token
|
||||
}
|
||||
p.Read() // eat keyword token
|
||||
|
||||
switch {
|
||||
|
||||
case ifKeyword.TokenMatches(kw):
|
||||
condExpr, exprDiags := p.ParseExpression()
|
||||
diags = append(diags, exprDiags...)
|
||||
parts = append(parts, &templateIfToken{
|
||||
CondExpr: condExpr,
|
||||
SrcRange: zcl.RangeBetween(next.Range, p.NextRange()),
|
||||
})
|
||||
|
||||
case elseKeyword.TokenMatches(kw):
|
||||
parts = append(parts, &templateEndCtrlToken{
|
||||
Type: templateElse,
|
||||
SrcRange: zcl.RangeBetween(next.Range, p.NextRange()),
|
||||
})
|
||||
|
||||
case endifKeyword.TokenMatches(kw):
|
||||
parts = append(parts, &templateEndCtrlToken{
|
||||
Type: templateEndIf,
|
||||
SrcRange: zcl.RangeBetween(next.Range, p.NextRange()),
|
||||
})
|
||||
|
||||
default:
|
||||
if !p.recovery {
|
||||
suggestions := []string{"if", "for", "else", "endif", "endfor"}
|
||||
given := string(kw.Bytes)
|
||||
suggestion := nameSuggestion(given, suggestions)
|
||||
if suggestion != "" {
|
||||
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
||||
}
|
||||
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Invalid template control keyword",
|
||||
Detail: fmt.Sprintf("%q is not a valid template control keyword.%s", given, suggestion),
|
||||
Subject: &kw.Range,
|
||||
Context: zcl.RangeBetween(next.Range, kw.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
p.PopIncludeNewlines()
|
||||
continue Token
|
||||
|
||||
}
|
||||
|
||||
close := p.Peek()
|
||||
if close.Type != TokenTemplateSeqEnd {
|
||||
if !p.recovery {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: fmt.Sprintf("Extra characters in %s marker", kw.Bytes),
|
||||
Detail: "Expected a closing brace to end the sequence, but found extra characters.",
|
||||
Subject: &close.Range,
|
||||
Context: zcl.RangeBetween(startRange, close.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
} else {
|
||||
p.Read() // eat closing brace
|
||||
|
||||
// If the closer is ~} then we want to eat any leading
|
||||
// whitespace on the next token, if it turns out to be a
|
||||
// literal token.
|
||||
if len(close.Bytes) == 2 && close.Bytes[0] == '~' {
|
||||
ltrimNext = true
|
||||
}
|
||||
}
|
||||
p.PopIncludeNewlines()
|
||||
|
||||
default:
|
||||
if !p.recovery {
|
||||
|
Loading…
Reference in New Issue
Block a user