Skip to content

Commit

Permalink
⭐ number.inRange support (#2952)
Browse files Browse the repository at this point in the history
Support a simpler methods to see if a number is in range.

So far users have to:

```coffee
num >= 3 && num <= 5
```

Now you can:

```coffee
num.inRange(3, 5)
```

This supports both integers and floats. Dict support is coming next.

Signed-off-by: Dominik Richter <[email protected]>
  • Loading branch information
arlimus authored Jan 6, 2024
1 parent 2fc993b commit 6c9a3f0
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 0 deletions.
2 changes: 2 additions & 0 deletions llx/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ func init() {
string("||" + types.ArrayLike): {f: intOrArrayV2, Label: "||"},
string("&&" + types.MapLike): {f: intAndMapV2, Label: "&&"},
string("||" + types.MapLike): {f: intOrMapV2, Label: "||"},
string("inRange"): {f: intInRange, Label: "inRange"},
},
types.Float: {
// == / !=
Expand Down Expand Up @@ -246,6 +247,7 @@ func init() {
string("||" + types.ArrayLike): {f: floatOrArrayV2, Label: "||"},
string("&&" + types.MapLike): {f: floatAndMapV2, Label: "&&"},
string("||" + types.MapLike): {f: floatOrMapV2, Label: "&&"},
string("inRange"): {f: floatInRange, Label: "inRange"},
},
types.String: {
// == / !=
Expand Down
86 changes: 86 additions & 0 deletions llx/builtin_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -1568,6 +1568,92 @@ func mapOrIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*Raw
return boolOpV2(e, bind, chunk, ref, opMapOrInt)
}

func int64InRange(e *blockExecutor, val int64, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
minRef := chunk.Function.Args[0]
min, rref, err := e.resolveValue(minRef, ref)
if err != nil || rref > 0 {
return nil, rref, err
}

switch minval := min.Value.(type) {
case int64:
if val < minval {
return BoolFalse, 0, nil
}
case float64:
if float64(val) < minval {
return BoolFalse, 0, nil
}
}

maxRef := chunk.Function.Args[1]
max, rref, err := e.resolveValue(maxRef, ref)
if err != nil || rref > 0 {
return nil, rref, err
}

switch maxval := max.Value.(type) {
case int64:
if val > maxval {
return BoolFalse, 0, nil
}
case float64:
if float64(val) > maxval {
return BoolFalse, 0, nil
}
}

return BoolTrue, 0, nil
}

func float64InRange(e *blockExecutor, val float64, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
minRef := chunk.Function.Args[0]
min, rref, err := e.resolveValue(minRef, ref)
if err != nil || rref > 0 {
return nil, rref, err
}

switch minval := min.Value.(type) {
case int64:
if val < float64(minval) {
return BoolFalse, 0, nil
}
case float64:
if val < minval {
return BoolFalse, 0, nil
}
}

maxRef := chunk.Function.Args[1]
max, rref, err := e.resolveValue(maxRef, ref)
if err != nil || rref > 0 {
return nil, rref, err
}

switch maxval := max.Value.(type) {
case int64:
if val > float64(maxval) {
return BoolFalse, 0, nil
}
case float64:
if val > maxval {
return BoolFalse, 0, nil
}
}

return BoolTrue, 0, nil
}

func intInRange(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
val := bind.Value.(int64)
return int64InRange(e, val, chunk, ref)
}

func floatInRange(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
val := bind.Value.(float64)
return float64InRange(e, val, chunk, ref)
}

// float &&/|| T

func opFloatAndString(left interface{}, right interface{}) bool {
Expand Down
6 changes: 6 additions & 0 deletions mqlc/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ var builtinFunctions map[types.Type]map[string]compileHandler

func init() {
builtinFunctions = map[types.Type]map[string]compileHandler{
types.Int: {
"inRange": {typ: boolType, compile: compileInRange},
},
types.Float: {
"inRange": {typ: boolType, compile: compileInRange},
},
types.String: {
"contains": {compile: compileStringContains, typ: boolType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.String}}},
"find": {typ: stringArrayType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.Regex}}},
Expand Down
60 changes: 60 additions & 0 deletions mqlc/builtin_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package mqlc

import (
"errors"
"strconv"

"go.mondoo.com/cnquery/v9/llx"
"go.mondoo.com/cnquery/v9/mqlc/parser"
Expand Down Expand Up @@ -102,3 +103,62 @@ func compileStringContains(c *compiler, typ types.Type, ref uint64, id string, c
return types.Nil, errors.New("cannot find #string.contains with this type " + types.Type(val.Type).Label())
}
}

func callArgTypeIs(c *compiler, call *parser.Call, id string, argName string, idx int, types ...types.Type) (*llx.Primitive, error) {
if len(call.Function) <= idx {
return nil, errors.New("function " + id + " is missing a " + argName + " (arg #" + strconv.Itoa(idx+1) + ")")
}

arg := call.Function[idx]
if arg.Value == nil || arg.Value.Operand == nil {
return nil, errors.New("function " + id + " is missing a " + argName + " (arg #" + strconv.Itoa(idx+1) + " is null)")
}

val, err := c.compileOperand(arg.Value.Operand)
if err != nil {
return nil, err
}

valType, err := c.dereferenceType(val)
if err != nil {
return nil, err
}

for _, t := range types {
if t == valType {
return val, nil
}
}

var typesStr string
for _, t := range types {
typesStr += t.Label() + "/"
}
return nil, errors.New("function " + id + " type mismatch for " + argName + " (expected: " + typesStr[0:len(typesStr)-1] + ", got: " + valType.Label() + ")")
}

func compileInRange(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) {
if call == nil || len(call.Function) != 2 {
return types.Nil, errors.New("function " + id + " needs two arguments (function missing)")
}

min, err := callArgTypeIs(c, call, id, "min", 0, types.Int, types.Float, types.Dict)
if err != nil {
return types.Nil, err
}
max, err := callArgTypeIs(c, call, id, "max", 1, types.Int, types.Float, types.Dict)
if err != nil {
return types.Nil, err
}

c.addChunk(&llx.Chunk{
Call: llx.Chunk_FUNCTION,
Id: "inRange",
Function: &llx.Function{
Type: string(types.Bool),
Binding: ref,
Args: []*llx.Primitive{min, max},
},
})
return types.Bool, nil
}
6 changes: 6 additions & 0 deletions providers/core/resources/mql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,12 @@ func TestNumber_Methods(t *testing.T) {
{
Code: "1 == NaN", Expectation: false,
},
{
Code: "2.inRange(1,2.0)", Expectation: true,
},
{
Code: "3.0.inRange(1.0,2)", Expectation: false,
},
})
}

Expand Down

0 comments on commit 6c9a3f0

Please sign in to comment.