1package hclsyntax
2
3import (
4	"fmt"
5
6	"github.com/hashicorp/hcl2/hcl"
7	"github.com/zclconf/go-cty/cty"
8	"github.com/zclconf/go-cty/cty/convert"
9	"github.com/zclconf/go-cty/cty/function"
10	"github.com/zclconf/go-cty/cty/function/stdlib"
11)
12
13type Operation struct {
14	Impl function.Function
15	Type cty.Type
16}
17
18var (
19	OpLogicalOr = &Operation{
20		Impl: stdlib.OrFunc,
21		Type: cty.Bool,
22	}
23	OpLogicalAnd = &Operation{
24		Impl: stdlib.AndFunc,
25		Type: cty.Bool,
26	}
27	OpLogicalNot = &Operation{
28		Impl: stdlib.NotFunc,
29		Type: cty.Bool,
30	}
31
32	OpEqual = &Operation{
33		Impl: stdlib.EqualFunc,
34		Type: cty.Bool,
35	}
36	OpNotEqual = &Operation{
37		Impl: stdlib.NotEqualFunc,
38		Type: cty.Bool,
39	}
40
41	OpGreaterThan = &Operation{
42		Impl: stdlib.GreaterThanFunc,
43		Type: cty.Bool,
44	}
45	OpGreaterThanOrEqual = &Operation{
46		Impl: stdlib.GreaterThanOrEqualToFunc,
47		Type: cty.Bool,
48	}
49	OpLessThan = &Operation{
50		Impl: stdlib.LessThanFunc,
51		Type: cty.Bool,
52	}
53	OpLessThanOrEqual = &Operation{
54		Impl: stdlib.LessThanOrEqualToFunc,
55		Type: cty.Bool,
56	}
57
58	OpAdd = &Operation{
59		Impl: stdlib.AddFunc,
60		Type: cty.Number,
61	}
62	OpSubtract = &Operation{
63		Impl: stdlib.SubtractFunc,
64		Type: cty.Number,
65	}
66	OpMultiply = &Operation{
67		Impl: stdlib.MultiplyFunc,
68		Type: cty.Number,
69	}
70	OpDivide = &Operation{
71		Impl: stdlib.DivideFunc,
72		Type: cty.Number,
73	}
74	OpModulo = &Operation{
75		Impl: stdlib.ModuloFunc,
76		Type: cty.Number,
77	}
78	OpNegate = &Operation{
79		Impl: stdlib.NegateFunc,
80		Type: cty.Number,
81	}
82)
83
84var binaryOps []map[TokenType]*Operation
85
86func init() {
87	// This operation table maps from the operator's token type
88	// to the AST operation type. All expressions produced from
89	// binary operators are BinaryOp nodes.
90	//
91	// Binary operator groups are listed in order of precedence, with
92	// the *lowest* precedence first. Operators within the same group
93	// have left-to-right associativity.
94	binaryOps = []map[TokenType]*Operation{
95		{
96			TokenOr: OpLogicalOr,
97		},
98		{
99			TokenAnd: OpLogicalAnd,
100		},
101		{
102			TokenEqualOp:  OpEqual,
103			TokenNotEqual: OpNotEqual,
104		},
105		{
106			TokenGreaterThan:   OpGreaterThan,
107			TokenGreaterThanEq: OpGreaterThanOrEqual,
108			TokenLessThan:      OpLessThan,
109			TokenLessThanEq:    OpLessThanOrEqual,
110		},
111		{
112			TokenPlus:  OpAdd,
113			TokenMinus: OpSubtract,
114		},
115		{
116			TokenStar:    OpMultiply,
117			TokenSlash:   OpDivide,
118			TokenPercent: OpModulo,
119		},
120	}
121}
122
123type BinaryOpExpr struct {
124	LHS Expression
125	Op  *Operation
126	RHS Expression
127
128	SrcRange hcl.Range
129}
130
131func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) {
132	w(e.LHS)
133	w(e.RHS)
134}
135
136func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
137	impl := e.Op.Impl // assumed to be a function taking exactly two arguments
138	params := impl.Params()
139	lhsParam := params[0]
140	rhsParam := params[1]
141
142	var diags hcl.Diagnostics
143
144	givenLHSVal, lhsDiags := e.LHS.Value(ctx)
145	givenRHSVal, rhsDiags := e.RHS.Value(ctx)
146	diags = append(diags, lhsDiags...)
147	diags = append(diags, rhsDiags...)
148
149	lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type)
150	if err != nil {
151		diags = append(diags, &hcl.Diagnostic{
152			Severity:    hcl.DiagError,
153			Summary:     "Invalid operand",
154			Detail:      fmt.Sprintf("Unsuitable value for left operand: %s.", err),
155			Subject:     e.LHS.Range().Ptr(),
156			Context:     &e.SrcRange,
157			Expression:  e.LHS,
158			EvalContext: ctx,
159		})
160	}
161	rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type)
162	if err != nil {
163		diags = append(diags, &hcl.Diagnostic{
164			Severity:    hcl.DiagError,
165			Summary:     "Invalid operand",
166			Detail:      fmt.Sprintf("Unsuitable value for right operand: %s.", err),
167			Subject:     e.RHS.Range().Ptr(),
168			Context:     &e.SrcRange,
169			Expression:  e.RHS,
170			EvalContext: ctx,
171		})
172	}
173
174	if diags.HasErrors() {
175		// Don't actually try the call if we have errors already, since the
176		// this will probably just produce a confusing duplicative diagnostic.
177		return cty.UnknownVal(e.Op.Type), diags
178	}
179
180	args := []cty.Value{lhsVal, rhsVal}
181	result, err := impl.Call(args)
182	if err != nil {
183		diags = append(diags, &hcl.Diagnostic{
184			// FIXME: This diagnostic is useless.
185			Severity:    hcl.DiagError,
186			Summary:     "Operation failed",
187			Detail:      fmt.Sprintf("Error during operation: %s.", err),
188			Subject:     &e.SrcRange,
189			Expression:  e,
190			EvalContext: ctx,
191		})
192		return cty.UnknownVal(e.Op.Type), diags
193	}
194
195	return result, diags
196}
197
198func (e *BinaryOpExpr) Range() hcl.Range {
199	return e.SrcRange
200}
201
202func (e *BinaryOpExpr) StartRange() hcl.Range {
203	return e.LHS.StartRange()
204}
205
206type UnaryOpExpr struct {
207	Op  *Operation
208	Val Expression
209
210	SrcRange    hcl.Range
211	SymbolRange hcl.Range
212}
213
214func (e *UnaryOpExpr) walkChildNodes(w internalWalkFunc) {
215	w(e.Val)
216}
217
218func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
219	impl := e.Op.Impl // assumed to be a function taking exactly one argument
220	params := impl.Params()
221	param := params[0]
222
223	givenVal, diags := e.Val.Value(ctx)
224
225	val, err := convert.Convert(givenVal, param.Type)
226	if err != nil {
227		diags = append(diags, &hcl.Diagnostic{
228			Severity:    hcl.DiagError,
229			Summary:     "Invalid operand",
230			Detail:      fmt.Sprintf("Unsuitable value for unary operand: %s.", err),
231			Subject:     e.Val.Range().Ptr(),
232			Context:     &e.SrcRange,
233			Expression:  e.Val,
234			EvalContext: ctx,
235		})
236	}
237
238	if diags.HasErrors() {
239		// Don't actually try the call if we have errors already, since the
240		// this will probably just produce a confusing duplicative diagnostic.
241		return cty.UnknownVal(e.Op.Type), diags
242	}
243
244	args := []cty.Value{val}
245	result, err := impl.Call(args)
246	if err != nil {
247		diags = append(diags, &hcl.Diagnostic{
248			// FIXME: This diagnostic is useless.
249			Severity:    hcl.DiagError,
250			Summary:     "Operation failed",
251			Detail:      fmt.Sprintf("Error during operation: %s.", err),
252			Subject:     &e.SrcRange,
253			Expression:  e,
254			EvalContext: ctx,
255		})
256		return cty.UnknownVal(e.Op.Type), diags
257	}
258
259	return result, diags
260}
261
262func (e *UnaryOpExpr) Range() hcl.Range {
263	return e.SrcRange
264}
265
266func (e *UnaryOpExpr) StartRange() hcl.Range {
267	return e.SymbolRange
268}
269