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:
Martin Atkins 2017-05-31 08:37:17 -07:00
parent 3a0dec45a6
commit 2fc14b729a
3 changed files with 182 additions and 0 deletions

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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) {