Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⭐ add typemaps for simple types #3463

Merged
merged 2 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
Loading