Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/river/vm/op_binary.go
4095 views
1
package vm
2
3
import (
4
"fmt"
5
"math"
6
"reflect"
7
8
"github.com/grafana/agent/pkg/river/internal/value"
9
"github.com/grafana/agent/pkg/river/token"
10
)
11
12
func evalBinop(lhs value.Value, op token.Token, rhs value.Value) (value.Value, error) {
13
// TODO(rfratto): evalBinop should check for underflows and overflows
14
15
// We have special handling for EQ and NEQ since it's valid to attempt to
16
// compare values of any two types.
17
switch op {
18
case token.EQ:
19
return value.Bool(valuesEqual(lhs, rhs)), nil
20
case token.NEQ:
21
return value.Bool(!valuesEqual(lhs, rhs)), nil
22
}
23
24
// The type of lhs and rhs must be acceptable for the binary operator.
25
if !acceptableBinopType(lhs, op) {
26
return value.Null, value.Error{
27
Value: lhs,
28
Inner: fmt.Errorf("should be one of %v for binop %s, got %s", binopAllowedTypes[op], op, lhs.Type()),
29
}
30
} else if !acceptableBinopType(rhs, op) {
31
return value.Null, value.Error{
32
Value: rhs,
33
Inner: fmt.Errorf("should be one of %v for binop %s, got %s", binopAllowedTypes[op], op, rhs.Type()),
34
}
35
}
36
37
// At this point, regardless of the operator, lhs and rhs must have the same
38
// type.
39
if lhs.Type() != rhs.Type() {
40
return value.Null, value.TypeError{Value: rhs, Expected: lhs.Type()}
41
}
42
43
switch op {
44
case token.OR: // bool || bool
45
return value.Bool(lhs.Bool() || rhs.Bool()), nil
46
case token.AND: // bool && Bool
47
return value.Bool(lhs.Bool() && rhs.Bool()), nil
48
49
case token.ADD: // number + number, string + string
50
if lhs.Type() == value.TypeString {
51
return value.String(lhs.Text() + rhs.Text()), nil
52
}
53
54
lhsNum, rhsNum := lhs.Number(), rhs.Number()
55
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
56
case value.NumberKindUint:
57
return value.Uint(lhsNum.Uint() + rhsNum.Uint()), nil
58
case value.NumberKindInt:
59
return value.Int(lhsNum.Int() + rhsNum.Int()), nil
60
case value.NumberKindFloat:
61
return value.Float(lhsNum.Float() + rhsNum.Float()), nil
62
}
63
64
case token.SUB: // number - number
65
lhsNum, rhsNum := lhs.Number(), rhs.Number()
66
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
67
case value.NumberKindUint:
68
return value.Uint(lhsNum.Uint() - rhsNum.Uint()), nil
69
case value.NumberKindInt:
70
return value.Int(lhsNum.Int() - rhsNum.Int()), nil
71
case value.NumberKindFloat:
72
return value.Float(lhsNum.Float() - rhsNum.Float()), nil
73
}
74
75
case token.MUL: // number * number
76
lhsNum, rhsNum := lhs.Number(), rhs.Number()
77
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
78
case value.NumberKindUint:
79
return value.Uint(lhsNum.Uint() * rhsNum.Uint()), nil
80
case value.NumberKindInt:
81
return value.Int(lhsNum.Int() * rhsNum.Int()), nil
82
case value.NumberKindFloat:
83
return value.Float(lhsNum.Float() * rhsNum.Float()), nil
84
}
85
86
case token.DIV: // number / number
87
lhsNum, rhsNum := lhs.Number(), rhs.Number()
88
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
89
case value.NumberKindUint:
90
return value.Uint(lhsNum.Uint() / rhsNum.Uint()), nil
91
case value.NumberKindInt:
92
return value.Int(lhsNum.Int() / rhsNum.Int()), nil
93
case value.NumberKindFloat:
94
return value.Float(lhsNum.Float() / rhsNum.Float()), nil
95
}
96
97
case token.MOD: // number % number
98
lhsNum, rhsNum := lhs.Number(), rhs.Number()
99
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
100
case value.NumberKindUint:
101
return value.Uint(lhsNum.Uint() % rhsNum.Uint()), nil
102
case value.NumberKindInt:
103
return value.Int(lhsNum.Int() % rhsNum.Int()), nil
104
case value.NumberKindFloat:
105
return value.Float(math.Mod(lhsNum.Float(), rhsNum.Float())), nil
106
}
107
108
case token.POW: // number ^ number
109
lhsNum, rhsNum := lhs.Number(), rhs.Number()
110
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
111
case value.NumberKindUint:
112
return value.Uint(intPow(lhsNum.Uint(), rhsNum.Uint())), nil
113
case value.NumberKindInt:
114
return value.Int(intPow(lhsNum.Int(), rhsNum.Int())), nil
115
case value.NumberKindFloat:
116
return value.Float(math.Pow(lhsNum.Float(), rhsNum.Float())), nil
117
}
118
119
case token.LT: // number < number, string < string
120
// Check string first.
121
if lhs.Type() == value.TypeString {
122
return value.Bool(lhs.Text() < rhs.Text()), nil
123
}
124
125
// Not a string; must be a number.
126
lhsNum, rhsNum := lhs.Number(), rhs.Number()
127
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
128
case value.NumberKindUint:
129
return value.Bool(lhsNum.Uint() < rhsNum.Uint()), nil
130
case value.NumberKindInt:
131
return value.Bool(lhsNum.Int() < rhsNum.Int()), nil
132
case value.NumberKindFloat:
133
return value.Bool(lhsNum.Float() < rhsNum.Float()), nil
134
}
135
136
case token.GT: // number > number, string > string
137
// Check string first.
138
if lhs.Type() == value.TypeString {
139
return value.Bool(lhs.Text() > rhs.Text()), nil
140
}
141
142
// Not a string; must be a number.
143
lhsNum, rhsNum := lhs.Number(), rhs.Number()
144
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
145
case value.NumberKindUint:
146
return value.Bool(lhsNum.Uint() > rhsNum.Uint()), nil
147
case value.NumberKindInt:
148
return value.Bool(lhsNum.Int() > rhsNum.Int()), nil
149
case value.NumberKindFloat:
150
return value.Bool(lhsNum.Float() > rhsNum.Float()), nil
151
}
152
153
case token.LTE: // number <= number, string <= string
154
// Check string first.
155
if lhs.Type() == value.TypeString {
156
return value.Bool(lhs.Text() <= rhs.Text()), nil
157
}
158
159
// Not a string; must be a number.
160
lhsNum, rhsNum := lhs.Number(), rhs.Number()
161
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
162
case value.NumberKindUint:
163
return value.Bool(lhsNum.Uint() <= rhsNum.Uint()), nil
164
case value.NumberKindInt:
165
return value.Bool(lhsNum.Int() <= rhsNum.Int()), nil
166
case value.NumberKindFloat:
167
return value.Bool(lhsNum.Float() <= rhsNum.Float()), nil
168
}
169
170
case token.GTE: // number >= number, string >= string
171
// Check string first.
172
if lhs.Type() == value.TypeString {
173
return value.Bool(lhs.Text() >= rhs.Text()), nil
174
}
175
176
// Not a string; must be a number.
177
lhsNum, rhsNum := lhs.Number(), rhs.Number()
178
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
179
case value.NumberKindUint:
180
return value.Bool(lhsNum.Uint() >= rhsNum.Uint()), nil
181
case value.NumberKindInt:
182
return value.Bool(lhsNum.Int() >= rhsNum.Int()), nil
183
case value.NumberKindFloat:
184
return value.Bool(lhsNum.Float() >= rhsNum.Float()), nil
185
}
186
}
187
188
panic("river/vm: unreachable")
189
}
190
191
// valuesEqual returns true if two River Values are equal.
192
func valuesEqual(lhs value.Value, rhs value.Value) bool {
193
if lhs.Type() != rhs.Type() {
194
// Two values with different types are never equal.
195
return false
196
}
197
198
switch lhs.Type() {
199
case value.TypeNull:
200
// Nothing to compare here: both lhs and rhs have the null type,
201
// so they're equal.
202
return true
203
204
case value.TypeNumber:
205
// Two numbers are equal if they have equal values. However, we have to
206
// determine what comparison we want to do and upcast the values to a
207
// different Go type as needed (so that 3 == 3.0 is true).
208
lhsNum, rhsNum := lhs.Number(), rhs.Number()
209
switch fitNumberKinds(lhsNum.Kind(), rhsNum.Kind()) {
210
case value.NumberKindUint:
211
return lhsNum.Uint() == rhsNum.Uint()
212
case value.NumberKindInt:
213
return lhsNum.Int() == rhsNum.Int()
214
case value.NumberKindFloat:
215
return lhsNum.Float() == rhsNum.Float()
216
}
217
218
case value.TypeString:
219
return lhs.Text() == rhs.Text()
220
221
case value.TypeBool:
222
return lhs.Bool() == rhs.Bool()
223
224
case value.TypeArray:
225
// Two arrays are equal if they have equal elements.
226
if lhs.Len() != rhs.Len() {
227
return false
228
}
229
for i := 0; i < lhs.Len(); i++ {
230
if !valuesEqual(lhs.Index(i), rhs.Index(i)) {
231
return false
232
}
233
}
234
return true
235
236
case value.TypeObject:
237
// Two objects are equal if they have equal elements.
238
if lhs.Len() != rhs.Len() {
239
return false
240
}
241
for _, key := range lhs.Keys() {
242
lhsElement, _ := lhs.Key(key)
243
rhsElement, inRHS := rhs.Key(key)
244
if !inRHS {
245
return false
246
}
247
if !valuesEqual(lhsElement, rhsElement) {
248
return false
249
}
250
}
251
return true
252
253
case value.TypeFunction:
254
// Two functions are never equal. We can't compare functions in Go, so
255
// there's no way to compare them in River right now.
256
return false
257
258
case value.TypeCapsule:
259
// Two capsules are only equal if the underlying values are deeply equal.
260
return reflect.DeepEqual(lhs.Interface(), rhs.Interface())
261
}
262
263
panic("river/vm: unreachable")
264
}
265
266
// binopAllowedTypes maps what type of values are permitted for a specific
267
// binary operation.
268
//
269
// token.EQ and token.NEQ are not included as they're handled separately from
270
// other binary ops.
271
var binopAllowedTypes = map[token.Token][]value.Type{
272
token.OR: {value.TypeBool},
273
token.AND: {value.TypeBool},
274
275
token.ADD: {value.TypeNumber, value.TypeString},
276
token.SUB: {value.TypeNumber},
277
token.MUL: {value.TypeNumber},
278
token.DIV: {value.TypeNumber},
279
token.MOD: {value.TypeNumber},
280
token.POW: {value.TypeNumber},
281
282
token.LT: {value.TypeNumber, value.TypeString},
283
token.GT: {value.TypeNumber, value.TypeString},
284
token.LTE: {value.TypeNumber, value.TypeString},
285
token.GTE: {value.TypeNumber, value.TypeString},
286
}
287
288
func acceptableBinopType(val value.Value, op token.Token) bool {
289
allowed, ok := binopAllowedTypes[op]
290
if !ok {
291
panic("river/vm: unexpected binop type")
292
}
293
294
actualType := val.Type()
295
for _, allowType := range allowed {
296
if allowType == actualType {
297
return true
298
}
299
}
300
return false
301
}
302
303
func fitNumberKinds(a, b value.NumberKind) value.NumberKind {
304
aPrec, bPrec := numberKindPrec[a], numberKindPrec[b]
305
if aPrec > bPrec {
306
return a
307
}
308
return b
309
}
310
311
var numberKindPrec = map[value.NumberKind]int{
312
value.NumberKindUint: 0,
313
value.NumberKindInt: 1,
314
value.NumberKindFloat: 2,
315
}
316
317
func intPow[Number int64 | uint64](n, m Number) Number {
318
if m == 0 {
319
return 1
320
}
321
result := n
322
for i := Number(2); i <= m; i++ {
323
result *= n
324
}
325
return result
326
}
327
328