Skip to content

Commit

Permalink
⭐ add typemaps for simple types (#3463)
Browse files Browse the repository at this point in the history
* ⭐ add typemaps for simple types

Basically:

```coffee
> int(1.23)
1

> bool(1)
true

> float(12)
12

> string(1.89)
"1.89"

> regex("w.r.d") == "world 🌎"
/w.r.d/
```

Signed-off-by: Dominik Richter <[email protected]>

* 🟢 separate typemaps from resource calls

Regex is the biggest offender right now and we need a long-term solution for this

Signed-off-by: Dominik Richter <[email protected]>

---------

Signed-off-by: Dominik Richter <[email protected]>
  • Loading branch information
arlimus authored Mar 2, 2024
1 parent 7dc1124 commit 49857bb
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 37 deletions.
171 changes: 170 additions & 1 deletion llx/builtin_global.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,17 @@ func init() {
"switch": switchCallV2,
"score": scoreCallV2,
"typeof": typeofCallV2,
"semver": semverCall,
"{}": blockV2,
"return": returnCallV2,
"createResource": globalCreateResource,
// type-conversions
"string": stringCall,
"$regex": regexCall, // TODO: support both the regex resource and the internal typemap!
"float": floatCall,
"int": intCall,
"bool": boolCall,
"dict": dictCall,
"semver": semverCall,
}
}

Expand Down Expand Up @@ -199,6 +206,168 @@ func semverCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, er
return &RawData{Type: types.Semver, Value: res.Value}, 0, nil
}

func stringCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) {
if len(f.Args) != 1 {
return nil, 0, errors.New("Called `string` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one")
}

res, dref, err := e.resolveValue(f.Args[0], ref)
if err != nil || dref != 0 || res == nil {
return res, dref, err
}

switch v := res.Value.(type) {
case string:
return StringData(v), 0, nil
case int64:
i := strconv.FormatInt(v, 10)
return StringData(i), 0, nil
case float64:
f := strconv.FormatFloat(v, 'f', 2, 64)
return StringData(f), 0, nil
case bool:
if v {
return StringData("true"), 0, nil
}
return StringData("false"), 0, nil
default:
return NilData, 0, nil
}
}

func regexCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) {
if len(f.Args) != 1 {
return nil, 0, errors.New("Called `regex` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one")
}

res, dref, err := e.resolveValue(f.Args[0], ref)
if err != nil || dref != 0 || res == nil {
return res, dref, err
}

switch v := res.Value.(type) {
case string:
return RegexData(v), 0, nil
case int64:
i := strconv.FormatInt(v, 10)
return RegexData(i), 0, nil
case float64:
f := strconv.FormatFloat(v, 'f', 2, 64)
return RegexData(f), 0, nil
case bool:
if v {
return RegexData("true"), 0, nil
}
return RegexData("false"), 0, nil
default:
return NilData, 0, nil
}
}

func intCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) {
if len(f.Args) != 1 {
return nil, 0, errors.New("Called `int` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one")
}

res, dref, err := e.resolveValue(f.Args[0], ref)
if err != nil || dref != 0 || res == nil {
return res, dref, err
}

switch v := res.Value.(type) {
case string:
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, 0, err
}
return IntData(i), 0, nil
case int64:
return IntData(v), 0, nil
case float64:
return IntData(int64(v)), 0, nil
case bool:
if v {
return IntData(1), 0, nil
}
return IntData(0), 0, nil
default:
return NilData, 0, nil
}
}

func floatCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) {
if len(f.Args) != 1 {
return nil, 0, errors.New("Called `float` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one")
}

res, dref, err := e.resolveValue(f.Args[0], ref)
if err != nil || dref != 0 || res == nil {
return res, dref, err
}

switch v := res.Value.(type) {
case string:
i, err := strconv.ParseFloat(v, 64)
if err != nil {
return nil, 0, err
}
return FloatData(i), 0, nil
case int64:
return FloatData(float64(v)), 0, nil
case float64:
return FloatData(v), 0, nil
case bool:
if v {
return FloatData(1), 0, nil
}
return FloatData(0), 0, nil
default:
return NilData, 0, nil
}
}

func boolCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) {
if len(f.Args) != 1 {
return nil, 0, errors.New("Called `bool` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one")
}

res, dref, err := e.resolveValue(f.Args[0], ref)
if err != nil || dref != 0 || res == nil {
return res, dref, err
}

switch v := res.Value.(type) {
case string:
return BoolData(v == "true"), 0, nil
case int64:
return BoolData(v != 0), 0, nil
case float64:
return BoolData(v != 0), 0, nil
case bool:
return BoolData(v), 0, nil
default:
return NilData, 0, nil
}
}

func dictCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) {
if len(f.Args) != 1 {
return nil, 0, errors.New("Called `dict` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one")
}

res, dref, err := e.resolveValue(f.Args[0], ref)
if err != nil || dref != 0 || res == nil {
return res, dref, err
}

switch v := res.Value.(type) {
case string, int64, float64, bool, []any, map[string]any:
return DictData(v), 0, nil
default:
return NilData, 0, nil
}
}

func expectV2(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) {
if len(f.Args) != 1 {
return nil, 0, errors.New("Called expect with " + strconv.Itoa(len(f.Args)) + " arguments, expected 1")
Expand Down
16 changes: 8 additions & 8 deletions llx/builtin_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@ func intLTStringV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*
return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData {
f, err := strconv.ParseInt(right.(string), 10, 64)
if err != nil {
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")}
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")}
}
return BoolData(left.(int64) < f)
})
Expand All @@ -1162,7 +1162,7 @@ func intLTEStringV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (
return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData {
f, err := strconv.ParseInt(right.(string), 10, 64)
if err != nil {
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")}
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")}
}
return BoolData(left.(int64) <= f)
})
Expand All @@ -1172,7 +1172,7 @@ func intGTStringV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*
return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData {
f, err := strconv.ParseInt(right.(string), 10, 64)
if err != nil {
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")}
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")}
}
return BoolData(left.(int64) > f)
})
Expand All @@ -1182,7 +1182,7 @@ func intGTEStringV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (
return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData {
f, err := strconv.ParseInt(right.(string), 10, 64)
if err != nil {
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")}
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")}
}
return BoolData(left.(int64) >= f)
})
Expand All @@ -1194,7 +1194,7 @@ func stringLTIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*
return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData {
f, err := strconv.ParseInt(left.(string), 10, 64)
if err != nil {
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")}
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")}
}
return BoolData(f < right.(int64))
})
Expand All @@ -1204,7 +1204,7 @@ func stringLTEIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (
return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData {
f, err := strconv.ParseInt(left.(string), 10, 64)
if err != nil {
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")}
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")}
}
return BoolData(f <= right.(int64))
})
Expand All @@ -1214,7 +1214,7 @@ func stringGTIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*
return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData {
f, err := strconv.ParseInt(left.(string), 10, 64)
if err != nil {
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")}
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")}
}
return BoolData(f > right.(int64))
})
Expand All @@ -1224,7 +1224,7 @@ func stringGTEIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (
return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData {
f, err := strconv.ParseInt(left.(string), 10, 64)
if err != nil {
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")}
return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")}
}
return BoolData(f >= right.(int64))
})
Expand Down
11 changes: 11 additions & 0 deletions mqlc/mqlc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,17 @@ func (c *compiler) compileIdentifier(id string, callBinding *variable, calls []*
return restCalls, variable.typ, nil
}

f = typeConversions[id]
if f != nil {
typ, err := f(c, id, call)
// If it works or is some random error, we are done. However, we
// try to toss this fish back in the sea if it's not a conversion.
// For example: regex.ipv4 can be handled below, since it's not a conversion
if err == nil || err != errNotConversion {
return restCalls, typ, err
}
}

found, restCalls, typ, err = c.compileResource(id, calls)
if found {
return restCalls, typ, err
Expand Down
28 changes: 0 additions & 28 deletions mqlc/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ func init() {
"switch": compileSwitch,
"Never": compileNever,
"empty": compileEmpty,
"semver": compileSemver,
}
}

Expand Down Expand Up @@ -497,33 +496,6 @@ func compileTypeof(c *compiler, id string, call *parser.Call) (types.Type, error
return types.String, nil
}

func compileSemver(c *compiler, id string, call *parser.Call) (types.Type, error) {
if call == nil || len(call.Function) < 1 {
return types.Nil, errors.New("missing parameter for '" + id + "', it requires 1")
}

arg := call.Function[0]
if arg == nil || arg.Value == nil || arg.Value.Operand == nil || arg.Value.Operand.Value == nil {
return types.Nil, errors.New("failed to get parameter for '" + id + "'")
}

argValue, err := c.compileExpression(arg.Value)
if err != nil {
return types.Nil, err
}

c.addChunk(&llx.Chunk{
Call: llx.Chunk_FUNCTION,
Id: "semver",
Function: &llx.Function{
Type: string(types.String),
Args: []*llx.Primitive{argValue},
},
})

return types.String, nil
}

func compileSwitch(c *compiler, id string, call *parser.Call) (types.Type, error) {
var ref *llx.Primitive

Expand Down
57 changes: 57 additions & 0 deletions mqlc/typemaps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package mqlc

import (
"errors"

"go.mondoo.com/cnquery/v10/llx"
"go.mondoo.com/cnquery/v10/mqlc/parser"
"go.mondoo.com/cnquery/v10/types"
)

var typeConversions map[string]fieldCompiler

func init() {
typeConversions = map[string]fieldCompiler{
"bool": compileTypeConversion("bool", types.Bool),
"int": compileTypeConversion("int", types.Int),
"float": compileTypeConversion("float", types.Float),
"string": compileTypeConversion("string", types.String),
"regex": compileTypeConversion("$regex", types.Regex),
"dict": compileTypeConversion("dict", types.Dict),
"semver": compileTypeConversion("semver", types.Semver),
}
}

var errNotConversion = errors.New("not a type-conversion")

func compileTypeConversion(llxID string, typ types.Type) fieldCompiler {
return func(c *compiler, id string, call *parser.Call) (types.Type, error) {
if call == nil || len(call.Function) < 1 {
return types.Nil, errNotConversion
}

arg := call.Function[0]
if arg == nil || arg.Value == nil || arg.Value.Operand == nil || arg.Value.Operand.Value == nil {
return types.Nil, errors.New("failed to get parameter for '" + id + "'")
}

argValue, err := c.compileExpression(arg.Value)
if err != nil {
return types.Nil, err
}

c.addChunk(&llx.Chunk{
Call: llx.Chunk_FUNCTION,
Id: llxID,
Function: &llx.Function{
Type: string(typ),
Args: []*llx.Primitive{argValue},
},
})

return types.String, nil
}
}

0 comments on commit 49857bb

Please sign in to comment.