209 lines
5.3 KiB
Go
209 lines
5.3 KiB
Go
package zclsyntax
|
||
|
||
import (
|
||
"fmt"
|
||
|
||
"github.com/zclconf/go-cty/cty"
|
||
"github.com/zclconf/go-cty/cty/convert"
|
||
"github.com/zclconf/go-cty/cty/function"
|
||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||
"github.com/zclconf/go-zcl/zcl"
|
||
)
|
||
|
||
type Operation rune
|
||
|
||
const (
|
||
OpNil Operation = 0 // Zero value of Operation. Not a valid Operation.
|
||
|
||
OpLogicalOr Operation = '∨'
|
||
OpLogicalAnd Operation = '∧'
|
||
OpLogicalNot Operation = '!'
|
||
OpEqual Operation = '='
|
||
OpNotEqual Operation = '≠'
|
||
OpGreaterThan Operation = '>'
|
||
OpGreaterThanOrEqual Operation = '≥'
|
||
OpLessThan Operation = '<'
|
||
OpLessThanOrEqual Operation = '≤'
|
||
OpAdd Operation = '+'
|
||
OpSubtract Operation = '-'
|
||
OpMultiply Operation = '*'
|
||
OpDivide Operation = '/'
|
||
OpModulo Operation = '%'
|
||
OpNegate Operation = '∓'
|
||
)
|
||
|
||
var binaryOps []map[TokenType]Operation
|
||
|
||
func init() {
|
||
// This operation table maps from the operator's token type
|
||
// to the AST operation type. All expressions produced from
|
||
// binary operators are BinaryOp nodes.
|
||
//
|
||
// Binary operator groups are listed in order of precedence, with
|
||
// the *lowest* precedence first. Operators within the same group
|
||
// have left-to-right associativity.
|
||
binaryOps = []map[TokenType]Operation{
|
||
{
|
||
TokenOr: OpLogicalOr,
|
||
},
|
||
{
|
||
TokenAnd: OpLogicalAnd,
|
||
},
|
||
{
|
||
TokenEqualOp: OpEqual,
|
||
TokenNotEqual: OpNotEqual,
|
||
},
|
||
{
|
||
TokenGreaterThan: OpGreaterThan,
|
||
TokenGreaterThanEq: OpGreaterThanOrEqual,
|
||
TokenLessThan: OpLessThan,
|
||
TokenLessThanEq: OpLessThanOrEqual,
|
||
},
|
||
{
|
||
TokenPlus: OpAdd,
|
||
TokenMinus: OpSubtract,
|
||
},
|
||
{
|
||
TokenStar: OpMultiply,
|
||
TokenSlash: OpDivide,
|
||
TokenPercent: OpModulo,
|
||
},
|
||
}
|
||
}
|
||
|
||
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 {
|
||
LHS Expression
|
||
Op Operation
|
||
RHS Expression
|
||
|
||
SrcRange zcl.Range
|
||
}
|
||
|
||
func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
||
e.LHS = w(e.LHS).(Expression)
|
||
e.RHS = w(e.LHS).(Expression)
|
||
}
|
||
|
||
func (e *BinaryOpExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||
impl := operationImpls[e.Op] // assumed to be a function taking exactly two arguments
|
||
params := impl.Params()
|
||
lhsParam := params[0]
|
||
rhsParam := params[1]
|
||
|
||
var diags zcl.Diagnostics
|
||
|
||
givenLHSVal, lhsDiags := e.LHS.Value(ctx)
|
||
givenRHSVal, rhsDiags := e.RHS.Value(ctx)
|
||
diags = append(diags, lhsDiags...)
|
||
diags = append(diags, rhsDiags...)
|
||
|
||
lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type)
|
||
if err != nil {
|
||
diags = append(diags, &zcl.Diagnostic{
|
||
Severity: zcl.DiagError,
|
||
Summary: "Invalid operand",
|
||
Detail: fmt.Sprintf("Unsuitable value for left operand: %s.", err),
|
||
Subject: e.LHS.Range().Ptr(),
|
||
Context: &e.SrcRange,
|
||
})
|
||
}
|
||
rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type)
|
||
if err != nil {
|
||
diags = append(diags, &zcl.Diagnostic{
|
||
Severity: zcl.DiagError,
|
||
Summary: "Invalid operand",
|
||
Detail: fmt.Sprintf("Unsuitable value for right operand: %s.", err),
|
||
Subject: e.RHS.Range().Ptr(),
|
||
Context: &e.SrcRange,
|
||
})
|
||
}
|
||
|
||
if diags.HasErrors() {
|
||
// Don't actually try the call if we have errors already, since the
|
||
// this will probably just produce a confusing duplicative diagnostic.
|
||
// Instead, we'll use the function's type check to figure out what
|
||
// 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}
|
||
result, err := impl.Call(args)
|
||
if err != nil {
|
||
diags = append(diags, &zcl.Diagnostic{
|
||
// FIXME: This diagnostic is useless.
|
||
Severity: zcl.DiagError,
|
||
Summary: "Operation failed",
|
||
Detail: fmt.Sprintf("Error during operation: %s.", err),
|
||
Subject: e.RHS.Range().Ptr(),
|
||
Context: &e.SrcRange,
|
||
})
|
||
retType, err := impl.ReturnTypeForValues(args)
|
||
if err != nil {
|
||
return cty.DynamicVal, diags
|
||
}
|
||
return cty.UnknownVal(retType), diags
|
||
}
|
||
|
||
return result, diags
|
||
}
|
||
|
||
func (e *BinaryOpExpr) Range() zcl.Range {
|
||
return e.SrcRange
|
||
}
|
||
|
||
func (e *BinaryOpExpr) StartRange() zcl.Range {
|
||
return e.LHS.StartRange()
|
||
}
|
||
|
||
type UnaryOpExpr struct {
|
||
Op Operation
|
||
Val Expression
|
||
|
||
SrcRange zcl.Range
|
||
SymbolRange zcl.Range
|
||
}
|
||
|
||
func (e *UnaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
||
e.Val = w(e.Val).(Expression)
|
||
}
|
||
|
||
func (e *UnaryOpExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||
panic("UnaryOpExpr.Value not yet implemented")
|
||
}
|
||
|
||
func (e *UnaryOpExpr) Range() zcl.Range {
|
||
return e.SrcRange
|
||
}
|
||
|
||
func (e *UnaryOpExpr) StartRange() zcl.Range {
|
||
return e.SymbolRange
|
||
}
|