zclsyntax: start of expression parsing
Conditional expression parsing is ostensibly implemented, but since it depends on the rest of the expression parsers -- not yet implemented -- it cannot be tested in isolation. Also includes an initial implementation of the conditional expression node, but this is also not yet tested and so may need further revisions once we're in a better position to test it.
This commit is contained in:
parent
3a0dec45a6
commit
2fc14b729a
@ -242,3 +242,112 @@ func (e *FunctionCallExpr) Range() zcl.Range {
|
||||
func (e *FunctionCallExpr) StartRange() zcl.Range {
|
||||
return zcl.RangeBetween(e.NameRange, e.OpenParenRange)
|
||||
}
|
||||
|
||||
type ConditionalExpr struct {
|
||||
Condition Expression
|
||||
TrueResult Expression
|
||||
FalseResult Expression
|
||||
|
||||
SrcRange zcl.Range
|
||||
}
|
||||
|
||||
func (e *ConditionalExpr) walkChildNodes(w internalWalkFunc) {
|
||||
e.Condition = w(e.Condition).(Expression)
|
||||
e.TrueResult = w(e.TrueResult).(Expression)
|
||||
e.FalseResult = w(e.FalseResult).(Expression)
|
||||
}
|
||||
|
||||
func (e *ConditionalExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||||
trueResult, trueDiags := e.TrueResult.Value(ctx)
|
||||
falseResult, falseDiags := e.FalseResult.Value(ctx)
|
||||
var diags zcl.Diagnostics
|
||||
|
||||
// Try to find a type that both results can be converted to.
|
||||
resultType, convs := convert.UnifyUnsafe([]cty.Type{trueResult.Type(), falseResult.Type()})
|
||||
if resultType == cty.NilType {
|
||||
return cty.DynamicVal, zcl.Diagnostics{
|
||||
{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Inconsistent conditional result types",
|
||||
Detail: fmt.Sprintf(
|
||||
// FIXME: Need a helper function for showing natural-language type diffs,
|
||||
// since this will generate some useless messages in some cases, like
|
||||
// "These expressions are object and object respectively" if the
|
||||
// object types don't exactly match.
|
||||
"The true and false result expressions must have consistent types. The given expressions are %s and %s, respectively.",
|
||||
trueResult.Type(), falseResult.Type(),
|
||||
),
|
||||
Subject: zcl.RangeBetween(e.TrueResult.Range(), e.FalseResult.Range()).Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
condResult, condDiags := e.Condition.Value(ctx)
|
||||
diags = append(diags, condDiags...)
|
||||
if condResult.IsNull() {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Null condition",
|
||||
Detail: "The condition value is null. Conditions must either be true or false.",
|
||||
Subject: e.Condition.Range().Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
})
|
||||
return cty.UnknownVal(resultType), diags
|
||||
}
|
||||
if !condResult.IsKnown() {
|
||||
return cty.UnknownVal(resultType), diags
|
||||
}
|
||||
|
||||
if condResult.True() {
|
||||
diags = append(diags, trueDiags...)
|
||||
if convs[0] != nil {
|
||||
var err error
|
||||
trueResult, err = convs[0](trueResult)
|
||||
if err != nil {
|
||||
// Unsafe conversion failed with the concrete result value
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Inconsistent conditional result types",
|
||||
Detail: fmt.Sprintf(
|
||||
"The true result value has the wrong type: %s.",
|
||||
err.Error(),
|
||||
),
|
||||
Subject: e.TrueResult.Range().Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
})
|
||||
trueResult = cty.UnknownVal(resultType)
|
||||
}
|
||||
}
|
||||
return trueResult, diags
|
||||
} else {
|
||||
diags = append(diags, falseDiags...)
|
||||
if convs[1] != nil {
|
||||
var err error
|
||||
falseResult, err = convs[1](falseResult)
|
||||
if err != nil {
|
||||
// Unsafe conversion failed with the concrete result value
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Inconsistent conditional result types",
|
||||
Detail: fmt.Sprintf(
|
||||
"The false result value has the wrong type: %s.",
|
||||
err.Error(),
|
||||
),
|
||||
Subject: e.TrueResult.Range().Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
})
|
||||
falseResult = cty.UnknownVal(resultType)
|
||||
}
|
||||
}
|
||||
return falseResult, diags
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ConditionalExpr) Range() zcl.Range {
|
||||
return e.SrcRange
|
||||
}
|
||||
|
||||
func (e *ConditionalExpr) StartRange() zcl.Range {
|
||||
return e.Condition.StartRange()
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ import (
|
||||
"github.com/zclconf/go-zcl/zcl"
|
||||
)
|
||||
|
||||
func (e *ConditionalExpr) Variables() []zcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpr) Variables() []zcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
@ -250,6 +250,75 @@ Token:
|
||||
}, diags
|
||||
}
|
||||
|
||||
func (p *parser) ParseExpression() (Expression, zcl.Diagnostics) {
|
||||
return p.parseTernaryConditional()
|
||||
}
|
||||
|
||||
func (p *parser) parseTernaryConditional() (Expression, zcl.Diagnostics) {
|
||||
// The ternary conditional operator (.. ? .. : ..) behaves somewhat
|
||||
// like a binary operator except that the "symbol" is itself
|
||||
// an expression enclosed in two punctuation characters.
|
||||
// The middle expression is parsed as if the ? and : symbols
|
||||
// were parentheses. The "rhs" (the "false expression") is then
|
||||
// treated right-associatively so it behaves similarly to the
|
||||
// middle in terms of precedence.
|
||||
|
||||
startRange := p.NextRange()
|
||||
var condExpr, trueExpr, falseExpr Expression
|
||||
var diags zcl.Diagnostics
|
||||
|
||||
condExpr, condDiags := p.parseBinaryOps(binaryOps)
|
||||
diags = append(diags, condDiags...)
|
||||
if p.recovery && condDiags.HasErrors() {
|
||||
return condExpr, diags
|
||||
}
|
||||
|
||||
questionMark := p.Peek()
|
||||
if questionMark.Type != TokenQuestion {
|
||||
return condExpr, diags
|
||||
}
|
||||
|
||||
p.Read() // eat question mark
|
||||
|
||||
trueExpr, trueDiags := p.ParseExpression()
|
||||
diags = append(diags, trueDiags...)
|
||||
if p.recovery && trueDiags.HasErrors() {
|
||||
return condExpr, diags
|
||||
}
|
||||
|
||||
colon := p.Peek()
|
||||
if colon.Type != TokenColon {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Missing false expression in conditional",
|
||||
Detail: "The conditional operator (...?...:...) requires a false expression, delimited by a colon.",
|
||||
Subject: &colon.Range,
|
||||
Context: zcl.RangeBetween(startRange, colon.Range).Ptr(),
|
||||
})
|
||||
return condExpr, diags
|
||||
}
|
||||
|
||||
p.Read() // eat colon
|
||||
|
||||
falseExpr, falseDiags := p.ParseExpression()
|
||||
diags = append(diags, falseDiags...)
|
||||
if p.recovery && falseDiags.HasErrors() {
|
||||
return condExpr, diags
|
||||
}
|
||||
|
||||
return &ConditionalExpr{
|
||||
Condition: condExpr,
|
||||
TrueResult: trueExpr,
|
||||
FalseResult: falseExpr,
|
||||
|
||||
SrcRange: zcl.RangeBetween(startRange, falseExpr.Range()),
|
||||
}, diags
|
||||
}
|
||||
|
||||
func (p *parser) parseBinaryOps(ops []map[TokenType]Operation) (Expression, zcl.Diagnostics) {
|
||||
panic("parseBinaryOps not yet implemented")
|
||||
}
|
||||
|
||||
// parseQuotedStringLiteral is a helper for parsing quoted strings that
|
||||
// aren't allowed to contain any interpolations, such as block labels.
|
||||
func (p *parser) parseQuotedStringLiteral() (string, zcl.Range, zcl.Diagnostics) {
|
||||
|
Loading…
Reference in New Issue
Block a user