Skip to content

Commit 8dfd410

Browse files
committed
Add type() builtin
1 parent b48b21c commit 8dfd410

File tree

6 files changed

+102
-1
lines changed

6 files changed

+102
-1
lines changed

builtin/builtin.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ var Builtins = []*Function{
4848
return anyType, fmt.Errorf("invalid argument for len (type %s)", args[0])
4949
},
5050
},
51+
{
52+
Name: "type",
53+
Builtin1: Type,
54+
Types: types(new(func(interface{}) string)),
55+
},
5156
{
5257
Name: "abs",
5358
Builtin1: Abs,

builtin/builtin_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,43 @@ func TestBuiltin_EnableBuiltin(t *testing.T) {
289289
assert.Equal(t, 6, out)
290290
})
291291
}
292+
293+
func TestBuiltin_type(t *testing.T) {
294+
type Foo struct{}
295+
var b interface{} = 1
296+
var a interface{} = &b
297+
tests := []struct {
298+
obj interface{}
299+
want string
300+
}{
301+
{nil, "nil"},
302+
{true, "bool"},
303+
{1, "int"},
304+
{int8(1), "int"},
305+
{uint(1), "uint"},
306+
{1.0, "float"},
307+
{float32(1.0), "float"},
308+
{"string", "string"},
309+
{[]string{"foo", "bar"}, "array"},
310+
{map[string]interface{}{"foo": "bar"}, "map"},
311+
{func() {}, "func"},
312+
{time.Now(), "time.Time"},
313+
{time.Second, "time.Duration"},
314+
{Foo{}, "github.com/antonmedv/expr/builtin_test.Foo"},
315+
{struct{}{}, "struct"},
316+
{a, "int"},
317+
}
318+
for _, test := range tests {
319+
t.Run(test.want, func(t *testing.T) {
320+
env := map[string]interface{}{
321+
"obj": test.obj,
322+
}
323+
program, err := expr.Compile(`type(obj)`, expr.Env(env))
324+
require.NoError(t, err)
325+
326+
out, err := expr.Run(program, env)
327+
require.NoError(t, err)
328+
assert.Equal(t, test.want, out)
329+
})
330+
}
331+
}

builtin/func.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,48 @@ func Len(x interface{}) interface{} {
1818
}
1919
}
2020

21+
func Type(arg interface{}) interface{} {
22+
if arg == nil {
23+
return "nil"
24+
}
25+
v := reflect.ValueOf(arg)
26+
for {
27+
if v.Kind() == reflect.Ptr {
28+
v = v.Elem()
29+
} else if v.Kind() == reflect.Interface {
30+
v = v.Elem()
31+
} else {
32+
break
33+
}
34+
}
35+
if v.Type().Name() != "" && v.Type().PkgPath() != "" {
36+
return fmt.Sprintf("%s.%s", v.Type().PkgPath(), v.Type().Name())
37+
}
38+
switch v.Type().Kind() {
39+
case reflect.Invalid:
40+
return "invalid"
41+
case reflect.Bool:
42+
return "bool"
43+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
44+
return "int"
45+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
46+
return "uint"
47+
case reflect.Float32, reflect.Float64:
48+
return "float"
49+
case reflect.String:
50+
return "string"
51+
case reflect.Array, reflect.Slice:
52+
return "array"
53+
case reflect.Map:
54+
return "map"
55+
case reflect.Func:
56+
return "func"
57+
case reflect.Struct:
58+
return "struct"
59+
}
60+
return "unknown"
61+
}
62+
2163
func Abs(x interface{}) interface{} {
2264
switch x.(type) {
2365
case float32:

builtin/utils.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ var (
88
anyType = reflect.TypeOf(new(interface{})).Elem()
99
integerType = reflect.TypeOf(0)
1010
floatType = reflect.TypeOf(float64(0))
11+
stringType = reflect.TypeOf("")
1112
arrayType = reflect.TypeOf([]interface{}{})
1213
)
1314

docs/Language-Definition.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,19 @@ len(filter(array, predicate))
237237

238238
Returns the length of an array, a map or a string.
239239

240+
### type(v)
241+
242+
Returns the type of the given value `v`.
243+
Returns on of the following types: `nil`, `bool`, `int`, `uint`, `float`, `string`, `array`, `map`.
244+
For named types and structs, the type name is returned.
245+
246+
247+
```expr
248+
type(42) == "int"
249+
type("hello") == "string"
250+
type(now()) == "time.Time"
251+
```
252+
240253
### abs(v)
241254

242255
Returns the absolute value of a number.

test/coredns/coredns_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestCoreDNS(t *testing.T) {
2525

2626
for _, test := range tests {
2727
t.Run(test.input, func(t *testing.T) {
28-
_, err := expr.Compile(test.input, expr.Env(env))
28+
_, err := expr.Compile(test.input, expr.Env(env), expr.DisableBuiltin("type"))
2929
assert.NoError(t, err)
3030
})
3131
}

0 commit comments

Comments
 (0)