zclsyntax: operations are now pointers to a struct value

Previously operations were an enum, but we've ended up needing to store
a collection of values against each, so an operation being a pointer to
a struct feels more natural.

This in turn allows us to more easily fix the return types of the
operations, so that we don't need to do any unusual work to understand
that (for example) arithmetic always returns a number.
This commit is contained in:
Martin Atkins 2017-06-12 07:09:24 -07:00
parent c332084224
commit e709d7bcc0
3 changed files with 153 additions and 66 deletions

View File

@ -10,29 +10,78 @@ import (
"github.com/zclconf/go-zcl/zcl" "github.com/zclconf/go-zcl/zcl"
) )
type Operation rune type Operation struct {
Impl function.Function
Type cty.Type
}
const ( var (
OpNil Operation = 0 // Zero value of Operation. Not a valid Operation. OpLogicalOr = &Operation{
Impl: stdlib.OrFunc,
Type: cty.Bool,
}
OpLogicalAnd = &Operation{
Impl: stdlib.AndFunc,
Type: cty.Bool,
}
OpLogicalNot = &Operation{
Impl: stdlib.NotFunc,
Type: cty.Bool,
}
OpLogicalOr Operation = '' OpEqual = &Operation{
OpLogicalAnd Operation = '∧' Impl: stdlib.EqualFunc,
OpLogicalNot Operation = '!' Type: cty.Bool,
OpEqual Operation = '=' }
OpNotEqual Operation = '≠' OpNotEqual = &Operation{
OpGreaterThan Operation = '>' Impl: stdlib.NotEqualFunc,
OpGreaterThanOrEqual Operation = '≥' Type: cty.Bool,
OpLessThan Operation = '<' }
OpLessThanOrEqual Operation = '≤'
OpAdd Operation = '+' OpGreaterThan = &Operation{
OpSubtract Operation = '-' Impl: stdlib.GreaterThanFunc,
OpMultiply Operation = '*' Type: cty.Bool,
OpDivide Operation = '/' }
OpModulo Operation = '%' OpGreaterThanOrEqual = &Operation{
OpNegate Operation = '∓' Impl: stdlib.GreaterThanOrEqualToFunc,
Type: cty.Bool,
}
OpLessThan = &Operation{
Impl: stdlib.LessThanFunc,
Type: cty.Bool,
}
OpLessThanOrEqual = &Operation{
Impl: stdlib.LessThanOrEqualToFunc,
Type: cty.Bool,
}
OpAdd = &Operation{
Impl: stdlib.AddFunc,
Type: cty.Number,
}
OpSubtract = &Operation{
Impl: stdlib.SubtractFunc,
Type: cty.Number,
}
OpMultiply = &Operation{
Impl: stdlib.MultiplyFunc,
Type: cty.Number,
}
OpDivide = &Operation{
Impl: stdlib.DivideFunc,
Type: cty.Number,
}
OpModulo = &Operation{
Impl: stdlib.ModuloFunc,
Type: cty.Number,
}
OpNegate = &Operation{
Impl: stdlib.NegateFunc,
Type: cty.Number,
}
) )
var binaryOps []map[TokenType]Operation var binaryOps []map[TokenType]*Operation
func init() { func init() {
// This operation table maps from the operator's token type // This operation table maps from the operator's token type
@ -42,7 +91,7 @@ func init() {
// Binary operator groups are listed in order of precedence, with // Binary operator groups are listed in order of precedence, with
// the *lowest* precedence first. Operators within the same group // the *lowest* precedence first. Operators within the same group
// have left-to-right associativity. // have left-to-right associativity.
binaryOps = []map[TokenType]Operation{ binaryOps = []map[TokenType]*Operation{
{ {
TokenOr: OpLogicalOr, TokenOr: OpLogicalOr,
}, },
@ -71,30 +120,9 @@ func init() {
} }
} }
var operationImpls = map[Operation]function.Function{
OpLogicalAnd: stdlib.AndFunc,
OpLogicalOr: stdlib.OrFunc,
OpLogicalNot: stdlib.NotFunc,
OpEqual: stdlib.EqualFunc,
OpNotEqual: stdlib.NotEqualFunc,
OpGreaterThan: stdlib.GreaterThanFunc,
OpGreaterThanOrEqual: stdlib.GreaterThanOrEqualToFunc,
OpLessThan: stdlib.LessThanFunc,
OpLessThanOrEqual: stdlib.LessThanOrEqualToFunc,
OpAdd: stdlib.AddFunc,
OpSubtract: stdlib.SubtractFunc,
OpMultiply: stdlib.MultiplyFunc,
OpDivide: stdlib.DivideFunc,
OpModulo: stdlib.ModuloFunc,
OpNegate: stdlib.NegateFunc,
}
type BinaryOpExpr struct { type BinaryOpExpr struct {
LHS Expression LHS Expression
Op Operation Op *Operation
RHS Expression RHS Expression
SrcRange zcl.Range SrcRange zcl.Range
@ -106,7 +134,7 @@ func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) {
} }
func (e *BinaryOpExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) { func (e *BinaryOpExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
impl := operationImpls[e.Op] // assumed to be a function taking exactly two arguments impl := e.Op.Impl // assumed to be a function taking exactly two arguments
params := impl.Params() params := impl.Params()
lhsParam := params[0] lhsParam := params[0]
rhsParam := params[1] rhsParam := params[1]
@ -142,16 +170,7 @@ func (e *BinaryOpExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics)
if diags.HasErrors() { if diags.HasErrors() {
// Don't actually try the call if we have errors already, since the // Don't actually try the call if we have errors already, since the
// this will probably just produce a confusing duplicative diagnostic. // this will probably just produce a confusing duplicative diagnostic.
// Instead, we'll use the function's type check to figure out what return cty.UnknownVal(e.Op.Type), diags
// type would be returned, if possible.
args := []cty.Value{givenLHSVal, givenRHSVal}
retType, err := impl.ReturnTypeForValues(args)
if err != nil {
// can't even get a return type, so we'll bail here.
return cty.DynamicVal, diags
}
return cty.UnknownVal(retType), diags
} }
args := []cty.Value{lhsVal, rhsVal} args := []cty.Value{lhsVal, rhsVal}
@ -165,11 +184,7 @@ func (e *BinaryOpExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics)
Subject: e.RHS.Range().Ptr(), Subject: e.RHS.Range().Ptr(),
Context: &e.SrcRange, Context: &e.SrcRange,
}) })
retType, err := impl.ReturnTypeForValues(args) return cty.UnknownVal(e.Op.Type), diags
if err != nil {
return cty.DynamicVal, diags
}
return cty.UnknownVal(retType), diags
} }
return result, diags return result, diags
@ -184,7 +199,7 @@ func (e *BinaryOpExpr) StartRange() zcl.Range {
} }
type UnaryOpExpr struct { type UnaryOpExpr struct {
Op Operation Op *Operation
Val Expression Val Expression
SrcRange zcl.Range SrcRange zcl.Range

View File

@ -36,11 +36,83 @@ func TestExpressionParseAndValue(t *testing.T) {
cty.NumberIntVal(5), cty.NumberIntVal(5),
0, 0,
}, },
{
`(2+unk)`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"unk": cty.UnknownVal(cty.Number),
},
},
cty.UnknownVal(cty.Number),
0,
},
{
`(2+unk)`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"unk": cty.DynamicVal,
},
},
cty.UnknownVal(cty.Number),
0,
},
{
`(unk+unk)`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"unk": cty.DynamicVal,
},
},
cty.UnknownVal(cty.Number),
0,
},
{ {
`(2+true)`, `(2+true)`,
nil, nil,
cty.DynamicVal, // FIXME: should be cty.UnknownVal(cty.Number) cty.UnknownVal(cty.Number),
1, 1, // unsuitable type for right operand
},
{
`(false+true)`,
nil,
cty.UnknownVal(cty.Number),
2, // unsuitable type for each operand
},
{
`(5 == 5)`,
nil,
cty.True,
0,
},
{
`(5 == 4)`,
nil,
cty.False,
0,
},
{
`(1 == true)`,
nil,
cty.False,
0,
},
{
`("true" == true)`,
nil,
cty.False,
0,
},
{
`(true == "true")`,
nil,
cty.False,
0,
},
{
`(true != "true")`,
nil,
cty.True,
0,
}, },
{ {
`( `(

View File

@ -400,7 +400,7 @@ func (p *parser) parseTernaryConditional() (Expression, zcl.Diagnostics) {
// parseBinaryOps calls itself recursively to work through all of the // parseBinaryOps calls itself recursively to work through all of the
// operator precedence groups, and then eventually calls parseExpressionTerm // operator precedence groups, and then eventually calls parseExpressionTerm
// for each operand. // for each operand.
func (p *parser) parseBinaryOps(ops []map[TokenType]Operation) (Expression, zcl.Diagnostics) { func (p *parser) parseBinaryOps(ops []map[TokenType]*Operation) (Expression, zcl.Diagnostics) {
if len(ops) == 0 { if len(ops) == 0 {
// We've run out of operators, so now we'll just try to parse a term. // We've run out of operators, so now we'll just try to parse a term.
return p.parseExpressionWithTraversals() return p.parseExpressionWithTraversals()
@ -410,7 +410,7 @@ func (p *parser) parseBinaryOps(ops []map[TokenType]Operation) (Expression, zcl.
remaining := ops[1:] remaining := ops[1:]
var lhs, rhs Expression var lhs, rhs Expression
operation := OpNil var operation *Operation
var diags zcl.Diagnostics var diags zcl.Diagnostics
// Parse a term that might be the first operand of a binary // Parse a term that might be the first operand of a binary
@ -432,14 +432,14 @@ func (p *parser) parseBinaryOps(ops []map[TokenType]Operation) (Expression, zcl.
// instead of iteratively parsing only the remaining operators. // instead of iteratively parsing only the remaining operators.
for { for {
next := p.Peek() next := p.Peek()
var newOp Operation var newOp *Operation
var ok bool var ok bool
if newOp, ok = thisLevel[next.Type]; !ok { if newOp, ok = thisLevel[next.Type]; !ok {
break break
} }
// Are we extending an expression started on the previous iteration? // Are we extending an expression started on the previous iteration?
if operation != OpNil { if operation != nil {
lhs = &BinaryOpExpr{ lhs = &BinaryOpExpr{
LHS: lhs, LHS: lhs,
Op: operation, Op: operation,
@ -459,7 +459,7 @@ func (p *parser) parseBinaryOps(ops []map[TokenType]Operation) (Expression, zcl.
} }
} }
if operation == OpNil { if operation == nil {
return lhs, diags return lhs, diags
} }