package hil import ( "errors" "strconv" "github.com/hashicorp/hil/ast" ) // NOTE: All builtins are tested in engine_test.go func registerBuiltins(scope *ast.BasicScope) *ast.BasicScope { if scope == nil { scope = new(ast.BasicScope) } if scope.FuncMap == nil { scope.FuncMap = make(map[string]ast.Function) } // Implicit conversions scope.FuncMap["__builtin_BoolToString"] = builtinBoolToString() scope.FuncMap["__builtin_FloatToInt"] = builtinFloatToInt() scope.FuncMap["__builtin_FloatToString"] = builtinFloatToString() scope.FuncMap["__builtin_IntToFloat"] = builtinIntToFloat() scope.FuncMap["__builtin_IntToString"] = builtinIntToString() scope.FuncMap["__builtin_StringToInt"] = builtinStringToInt() scope.FuncMap["__builtin_StringToFloat"] = builtinStringToFloat() scope.FuncMap["__builtin_StringToBool"] = builtinStringToBool() // Math operations scope.FuncMap["__builtin_IntMath"] = builtinIntMath() scope.FuncMap["__builtin_FloatMath"] = builtinFloatMath() scope.FuncMap["__builtin_BoolCompare"] = builtinBoolCompare() scope.FuncMap["__builtin_FloatCompare"] = builtinFloatCompare() scope.FuncMap["__builtin_IntCompare"] = builtinIntCompare() scope.FuncMap["__builtin_StringCompare"] = builtinStringCompare() scope.FuncMap["__builtin_Logical"] = builtinLogical() return scope } func builtinFloatMath() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt}, Variadic: true, VariadicType: ast.TypeFloat, ReturnType: ast.TypeFloat, Callback: func(args []interface{}) (interface{}, error) { op := args[0].(ast.ArithmeticOp) result := args[1].(float64) for _, raw := range args[2:] { arg := raw.(float64) switch op { case ast.ArithmeticOpAdd: result += arg case ast.ArithmeticOpSub: result -= arg case ast.ArithmeticOpMul: result *= arg case ast.ArithmeticOpDiv: result /= arg } } return result, nil }, } } func builtinIntMath() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt}, Variadic: true, VariadicType: ast.TypeInt, ReturnType: ast.TypeInt, Callback: func(args []interface{}) (interface{}, error) { op := args[0].(ast.ArithmeticOp) result := args[1].(int) for _, raw := range args[2:] { arg := raw.(int) switch op { case ast.ArithmeticOpAdd: result += arg case ast.ArithmeticOpSub: result -= arg case ast.ArithmeticOpMul: result *= arg case ast.ArithmeticOpDiv: if arg == 0 { return nil, errors.New("divide by zero") } result /= arg case ast.ArithmeticOpMod: if arg == 0 { return nil, errors.New("divide by zero") } result = result % arg } } return result, nil }, } } func builtinBoolCompare() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt, ast.TypeBool, ast.TypeBool}, Variadic: false, ReturnType: ast.TypeBool, Callback: func(args []interface{}) (interface{}, error) { op := args[0].(ast.ArithmeticOp) lhs := args[1].(bool) rhs := args[2].(bool) switch op { case ast.ArithmeticOpEqual: return lhs == rhs, nil case ast.ArithmeticOpNotEqual: return lhs != rhs, nil default: return nil, errors.New("invalid comparison operation") } }, } } func builtinFloatCompare() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt, ast.TypeFloat, ast.TypeFloat}, Variadic: false, ReturnType: ast.TypeBool, Callback: func(args []interface{}) (interface{}, error) { op := args[0].(ast.ArithmeticOp) lhs := args[1].(float64) rhs := args[2].(float64) switch op { case ast.ArithmeticOpEqual: return lhs == rhs, nil case ast.ArithmeticOpNotEqual: return lhs != rhs, nil case ast.ArithmeticOpLessThan: return lhs < rhs, nil case ast.ArithmeticOpLessThanOrEqual: return lhs <= rhs, nil case ast.ArithmeticOpGreaterThan: return lhs > rhs, nil case ast.ArithmeticOpGreaterThanOrEqual: return lhs >= rhs, nil default: return nil, errors.New("invalid comparison operation") } }, } } func builtinIntCompare() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt, ast.TypeInt, ast.TypeInt}, Variadic: false, ReturnType: ast.TypeBool, Callback: func(args []interface{}) (interface{}, error) { op := args[0].(ast.ArithmeticOp) lhs := args[1].(int) rhs := args[2].(int) switch op { case ast.ArithmeticOpEqual: return lhs == rhs, nil case ast.ArithmeticOpNotEqual: return lhs != rhs, nil case ast.ArithmeticOpLessThan: return lhs < rhs, nil case ast.ArithmeticOpLessThanOrEqual: return lhs <= rhs, nil case ast.ArithmeticOpGreaterThan: return lhs > rhs, nil case ast.ArithmeticOpGreaterThanOrEqual: return lhs >= rhs, nil default: return nil, errors.New("invalid comparison operation") } }, } } func builtinStringCompare() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt, ast.TypeString, ast.TypeString}, Variadic: false, ReturnType: ast.TypeBool, Callback: func(args []interface{}) (interface{}, error) { op := args[0].(ast.ArithmeticOp) lhs := args[1].(string) rhs := args[2].(string) switch op { case ast.ArithmeticOpEqual: return lhs == rhs, nil case ast.ArithmeticOpNotEqual: return lhs != rhs, nil default: return nil, errors.New("invalid comparison operation") } }, } } func builtinLogical() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt}, Variadic: true, VariadicType: ast.TypeBool, ReturnType: ast.TypeBool, Callback: func(args []interface{}) (interface{}, error) { op := args[0].(ast.ArithmeticOp) result := args[1].(bool) for _, raw := range args[2:] { arg := raw.(bool) switch op { case ast.ArithmeticOpLogicalOr: result = result || arg case ast.ArithmeticOpLogicalAnd: result = result && arg default: return nil, errors.New("invalid logical operator") } } return result, nil }, } } func builtinFloatToInt() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeFloat}, ReturnType: ast.TypeInt, Callback: func(args []interface{}) (interface{}, error) { return int(args[0].(float64)), nil }, } } func builtinFloatToString() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeFloat}, ReturnType: ast.TypeString, Callback: func(args []interface{}) (interface{}, error) { return strconv.FormatFloat( args[0].(float64), 'g', -1, 64), nil }, } } func builtinIntToFloat() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt}, ReturnType: ast.TypeFloat, Callback: func(args []interface{}) (interface{}, error) { return float64(args[0].(int)), nil }, } } func builtinIntToString() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt}, ReturnType: ast.TypeString, Callback: func(args []interface{}) (interface{}, error) { return strconv.FormatInt(int64(args[0].(int)), 10), nil }, } } func builtinStringToInt() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt}, ReturnType: ast.TypeString, Callback: func(args []interface{}) (interface{}, error) { v, err := strconv.ParseInt(args[0].(string), 0, 0) if err != nil { return nil, err } return int(v), nil }, } } func builtinStringToFloat() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeString}, ReturnType: ast.TypeFloat, Callback: func(args []interface{}) (interface{}, error) { v, err := strconv.ParseFloat(args[0].(string), 64) if err != nil { return nil, err } return v, nil }, } } func builtinBoolToString() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeBool}, ReturnType: ast.TypeString, Callback: func(args []interface{}) (interface{}, error) { return strconv.FormatBool(args[0].(bool)), nil }, } } func builtinStringToBool() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeString}, ReturnType: ast.TypeBool, Callback: func(args []interface{}) (interface{}, error) { v, err := strconv.ParseBool(args[0].(string)) if err != nil { return nil, err } return v, nil }, } }