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
|
cty.StringVal("truetrimtrue"), // trimming is no-op of neighbors aren't literal strings
|
||||||
0,
|
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 {
|
for _, test := range tests {
|
||||||
|
@ -9,6 +9,9 @@ type Keyword []byte
|
|||||||
var forKeyword = Keyword([]byte{'f', 'o', 'r'})
|
var forKeyword = Keyword([]byte{'f', 'o', 'r'})
|
||||||
var inKeyword = Keyword([]byte{'i', 'n'})
|
var inKeyword = Keyword([]byte{'i', 'n'})
|
||||||
var ifKeyword = Keyword([]byte{'i', 'f'})
|
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 {
|
func (kw Keyword) TokenMatches(token Token) bool {
|
||||||
if token.Type != TokenIdent {
|
if token.Type != TokenIdent {
|
||||||
|
@ -69,27 +69,29 @@ func (p *templateParser) parseRoot() ([]Expression, zcl.Diagnostics) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *templateParser) parseExpr() (Expression, zcl.Diagnostics) {
|
func (p *templateParser) parseExpr() (Expression, zcl.Diagnostics) {
|
||||||
next := p.Read()
|
next := p.Peek()
|
||||||
switch tok := next.(type) {
|
switch tok := next.(type) {
|
||||||
|
|
||||||
case *templateLiteralToken:
|
case *templateLiteralToken:
|
||||||
|
p.Read() // eat literal
|
||||||
return &LiteralValueExpr{
|
return &LiteralValueExpr{
|
||||||
Val: cty.StringVal(tok.Val),
|
Val: cty.StringVal(tok.Val),
|
||||||
SrcRange: tok.SrcRange,
|
SrcRange: tok.SrcRange,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
case *templateInterpToken:
|
case *templateInterpToken:
|
||||||
|
p.Read() // eat interp
|
||||||
return tok.Expr, nil
|
return tok.Expr, nil
|
||||||
|
|
||||||
case *templateIfToken:
|
case *templateIfToken:
|
||||||
// TODO: implement
|
return p.parseIf()
|
||||||
panic("template if token not yet implemented")
|
|
||||||
|
|
||||||
case *templateForToken:
|
case *templateForToken:
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
panic("template for token not yet implemented")
|
panic("template for token not yet implemented")
|
||||||
|
|
||||||
case *templateEndToken:
|
case *templateEndToken:
|
||||||
|
p.Read() // eat erroneous token
|
||||||
return errPlaceholderExpr(tok.SrcRange), zcl.Diagnostics{
|
return errPlaceholderExpr(tok.SrcRange), zcl.Diagnostics{
|
||||||
{
|
{
|
||||||
// This is a particularly unhelpful diagnostic, so callers
|
// This is a particularly unhelpful diagnostic, so callers
|
||||||
@ -103,6 +105,7 @@ func (p *templateParser) parseExpr() (Expression, zcl.Diagnostics) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case *templateEndCtrlToken:
|
case *templateEndCtrlToken:
|
||||||
|
p.Read() // eat erroneous token
|
||||||
return errPlaceholderExpr(tok.SrcRange), zcl.Diagnostics{
|
return errPlaceholderExpr(tok.SrcRange), zcl.Diagnostics{
|
||||||
{
|
{
|
||||||
Severity: zcl.DiagError,
|
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 {
|
func (p *templateParser) Peek() templateToken {
|
||||||
return p.Tokens[p.pos]
|
return p.Tokens[p.pos]
|
||||||
}
|
}
|
||||||
@ -214,8 +329,104 @@ Token:
|
|||||||
Expr: expr,
|
Expr: expr,
|
||||||
SrcRange: zcl.RangeBetween(next.Range, close.Range),
|
SrcRange: zcl.RangeBetween(next.Range, close.Range),
|
||||||
})
|
})
|
||||||
|
|
||||||
case TokenTemplateControl:
|
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:
|
default:
|
||||||
if !p.recovery {
|
if !p.recovery {
|
||||||
|
Loading…
Reference in New Issue
Block a user