2056 lines
35 KiB
Go
2056 lines
35 KiB
Go
package hil
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hil/ast"
|
|
)
|
|
|
|
func TestEval(t *testing.T) {
|
|
cases := []struct {
|
|
Input string
|
|
Scope *ast.BasicScope
|
|
Error bool
|
|
Result interface{}
|
|
ResultType EvalType
|
|
}{
|
|
{
|
|
Input: "Hello World",
|
|
Scope: nil,
|
|
Result: "Hello World",
|
|
ResultType: TypeString,
|
|
},
|
|
{
|
|
Input: `${"foo\\bar"}`,
|
|
Scope: nil,
|
|
Result: `foo\bar`,
|
|
ResultType: TypeString,
|
|
},
|
|
{
|
|
Input: `${"foo\\\\bar"}`,
|
|
Scope: nil,
|
|
Result: `foo\\bar`,
|
|
ResultType: TypeString,
|
|
},
|
|
{
|
|
"${var.alist}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
[]interface{}{"Hello", "World"},
|
|
TypeList,
|
|
},
|
|
{
|
|
"${var.alist[1]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"World",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${var.alist["1"]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"World",
|
|
TypeString,
|
|
},
|
|
{
|
|
"${var.alist[1]} ${var.alist[0]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"World Hello",
|
|
TypeString,
|
|
},
|
|
{
|
|
"${var.alist[2-1]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"World",
|
|
TypeString,
|
|
},
|
|
{
|
|
"${var.alist[1]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
Value: UnknownValue,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
"${var.alist[var.index]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
},
|
|
},
|
|
"var.index": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
Value: UnknownValue,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
"${var.alist} ${var.alist}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
true,
|
|
nil,
|
|
TypeInvalid,
|
|
},
|
|
{
|
|
"${var.alist[1]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
Value: UnknownValue,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"World",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": "hello",
|
|
"bar": "world",
|
|
},
|
|
TypeMap,
|
|
},
|
|
{
|
|
`${foo["bar"]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"world",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo["foo"]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"hello",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo["bar"]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
`${foo["foo"]} foo`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"hello foo",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo["bar"]} foo`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
|
|
{
|
|
`${foo[3]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"3": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"world",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo["bar"]} ${foo["foo"]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"world hello",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo} ${foo}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
true,
|
|
nil,
|
|
TypeInvalid,
|
|
},
|
|
{
|
|
`${foo} ${bar}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"Hello World",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo} ${bar}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 4,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"Hello 4",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo} ${bar}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
`${foo}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": "hello",
|
|
"bar": "world",
|
|
},
|
|
TypeMap,
|
|
},
|
|
{
|
|
"${var.alist}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
[]interface{}{
|
|
"Hello",
|
|
"World",
|
|
},
|
|
TypeList,
|
|
},
|
|
{
|
|
"${var.alist[0] + var.alist[1]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
Value: UnknownValue,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
// Unknowns can short-circuit bits of our type checking
|
|
// AST transform, such as the promotion of arithmetic to
|
|
// functions. This test ensures that the evaluator and the
|
|
// type checker co-operate to ensure that this doesn't cause
|
|
// raw arithmetic nodes to be evaluated (which is not supported).
|
|
"${var.alist[0 + var.unknown]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 2,
|
|
},
|
|
},
|
|
},
|
|
"var.unknown": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
Value: UnknownValue,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
"${join(var.alist)}",
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"join": ast.Function{
|
|
ArgTypes: []ast.Type{ast.TypeList},
|
|
ReturnType: ast.TypeString,
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
return nil, fmt.Errorf("should never actually be called")
|
|
},
|
|
},
|
|
},
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
Value: UnknownValue,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
"${upper(var.alist[1])}",
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"upper": ast.Function{
|
|
ArgTypes: []ast.Type{ast.TypeString},
|
|
ReturnType: ast.TypeString,
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
return strings.ToUpper(args[0].(string)), nil
|
|
},
|
|
},
|
|
},
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
Value: UnknownValue,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"WORLD",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo[upper(bar)]}`,
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"upper": ast.Function{
|
|
ArgTypes: []ast.Type{ast.TypeString},
|
|
ReturnType: ast.TypeString,
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
return strings.ToUpper(args[0].(string)), nil
|
|
},
|
|
},
|
|
},
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"KEY": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "value",
|
|
},
|
|
},
|
|
},
|
|
"bar": ast.Variable{
|
|
Value: "key",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"value",
|
|
TypeString,
|
|
},
|
|
{
|
|
`${foo[upper(bar)]}`,
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"upper": ast.Function{
|
|
ArgTypes: []ast.Type{ast.TypeString},
|
|
ReturnType: ast.TypeString,
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
return strings.ToUpper(args[0].(string)), nil
|
|
},
|
|
},
|
|
},
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"KEY": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "value",
|
|
},
|
|
},
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
`${upper(foo)}`,
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"upper": ast.Function{
|
|
ArgTypes: []ast.Type{ast.TypeMap},
|
|
ReturnType: ast.TypeString,
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
return "foo", nil
|
|
},
|
|
},
|
|
},
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"KEY": ast.Variable{
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
TypeUnknown,
|
|
},
|
|
{
|
|
Input: `${"foo\\"}`,
|
|
Scope: nil,
|
|
Result: `foo\`,
|
|
ResultType: TypeString,
|
|
},
|
|
{
|
|
Input: `${"foo\\\\"}`,
|
|
Scope: nil,
|
|
Result: `foo\\`,
|
|
ResultType: TypeString,
|
|
},
|
|
{
|
|
`${second("foo", "\\", "/", "bar")}`,
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"second": {
|
|
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString, ast.TypeString},
|
|
ReturnType: ast.TypeString,
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
return args[1].(string), nil
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
`\`,
|
|
TypeString,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
t.Run(fmt.Sprintf("%d-%s", i, tc.Input), func(t *testing.T) {
|
|
node, err := Parse(tc.Input)
|
|
if err != nil {
|
|
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
|
|
}
|
|
|
|
result, err := Eval(node, &EvalConfig{GlobalScope: tc.Scope})
|
|
if err != nil != tc.Error {
|
|
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
|
|
}
|
|
if tc.ResultType != TypeInvalid && result.Type != tc.ResultType {
|
|
t.Fatalf("Bad: %s\n\nInput: %s", result.Type, tc.Input)
|
|
}
|
|
if !reflect.DeepEqual(result.Value, tc.Result) {
|
|
t.Fatalf("\n Got: %#v\nExpected: %#v\n\n Input: %s\n", result.Value, tc.Result, tc.Input)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEvalInternal(t *testing.T) {
|
|
cases := []struct {
|
|
Input string
|
|
Scope *ast.BasicScope
|
|
Error bool
|
|
Result interface{}
|
|
ResultType ast.Type
|
|
}{
|
|
{
|
|
"foo",
|
|
nil,
|
|
false,
|
|
"foo",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${bar}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: "baz",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo baz",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${var.alist}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.alist": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
[]ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
ast.TypeList,
|
|
},
|
|
|
|
{
|
|
"foo ${-29}",
|
|
nil,
|
|
false,
|
|
"foo -29",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42+1}",
|
|
nil,
|
|
false,
|
|
"foo 43",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42-1}",
|
|
nil,
|
|
false,
|
|
"foo 41",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42*2}",
|
|
nil,
|
|
false,
|
|
"foo 84",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42/2}",
|
|
nil,
|
|
false,
|
|
"foo 21",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42/0}",
|
|
nil,
|
|
true,
|
|
"foo ",
|
|
ast.TypeInvalid,
|
|
},
|
|
|
|
{
|
|
"foo ${42%4}",
|
|
nil,
|
|
false,
|
|
"foo 2",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42%0}",
|
|
nil,
|
|
true,
|
|
"foo ",
|
|
ast.TypeInvalid,
|
|
},
|
|
|
|
{
|
|
"foo ${42.0+1.0}",
|
|
nil,
|
|
false,
|
|
"foo 43",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42.0+1}",
|
|
nil,
|
|
false,
|
|
"foo 43",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42+1.0}",
|
|
nil,
|
|
false,
|
|
"foo 43",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${0.5 * 75}",
|
|
nil,
|
|
false,
|
|
"foo 37.5",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${75 * 0.5}",
|
|
nil,
|
|
false,
|
|
"foo 37.5",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42+2*2}",
|
|
nil,
|
|
false,
|
|
"foo 46",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${42+(2*2)}",
|
|
nil,
|
|
false,
|
|
"foo 46",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${true && false}",
|
|
nil,
|
|
false,
|
|
"foo false",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${false || true}",
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${"true" || true}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${true || "true"}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${"true" || "true"}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${1 == 2}",
|
|
nil,
|
|
false,
|
|
"foo false",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${1 == 1}",
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${1 > 2}",
|
|
nil,
|
|
false,
|
|
"foo false",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${2 > 1}",
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${"hello" == "hello"}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${"hello" == "goodbye"}`,
|
|
nil,
|
|
false,
|
|
"foo false",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${1 == "2"}`,
|
|
nil,
|
|
false,
|
|
"foo false",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${1 == "1"}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${"1" == 1}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${1.2 == 1}`,
|
|
nil,
|
|
false,
|
|
"foo false",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
// implicit conversion of float to int makes this equal
|
|
`foo ${1 == 1.2}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${true == false}`,
|
|
nil,
|
|
false,
|
|
"foo false",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${false == false}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${"true" == true}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${true == "true"}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${! true}`,
|
|
nil,
|
|
false,
|
|
"foo false",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${! false}`,
|
|
nil,
|
|
false,
|
|
"foo true",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${true ? 5 : 7}",
|
|
nil,
|
|
false,
|
|
"foo 5",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${false ? 5 : 7}",
|
|
nil,
|
|
false,
|
|
"foo 7",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${"true" ? 5 : 7}`,
|
|
nil,
|
|
false,
|
|
"foo 5",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
// false expression is type-converted to match true expression
|
|
`foo ${false ? 5 : 6.5}`,
|
|
nil,
|
|
false,
|
|
"foo 6",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
// true expression is type-converted to match false expression
|
|
// if the true expression is string
|
|
`foo ${false ? "12" : 16}`,
|
|
nil,
|
|
false,
|
|
"foo 16",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${3 > 2 ? 5 : 7}",
|
|
nil,
|
|
false,
|
|
"foo 5",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${var.do_it ? 5 : 7}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.do_it": ast.Variable{
|
|
Value: UnknownValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
ast.TypeUnknown,
|
|
},
|
|
|
|
{
|
|
// false expression can be unknown, and is returned
|
|
`foo ${false ? "12" : unknown}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"unknown": ast.Variable{
|
|
Value: UnknownValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
ast.TypeUnknown,
|
|
},
|
|
|
|
{
|
|
// false expression can be unknown, and result is unknown even
|
|
// if it's not selected.
|
|
// (Ideally this would not be true, but we're accepting this
|
|
// for now since this assumption is built in to the core evaluator)
|
|
`foo ${true ? "12" : unknown}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"unknown": ast.Variable{
|
|
Value: UnknownValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
ast.TypeUnknown,
|
|
},
|
|
|
|
{
|
|
// true expression can be unknown, and is returned
|
|
`foo ${false ? unknown : "bar"}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"unknown": ast.Variable{
|
|
Value: UnknownValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
ast.TypeUnknown,
|
|
},
|
|
|
|
{
|
|
// true expression can be unknown, and result is unknown even
|
|
// if it's not selected.
|
|
// (Ideally this would not be true, but we're accepting this
|
|
// for now since this assumption is built in to the core evaluator)
|
|
`foo ${false ? unknown : "bar"}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"unknown": ast.Variable{
|
|
Value: UnknownValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
ast.TypeUnknown,
|
|
},
|
|
|
|
{
|
|
// both values can be unknown
|
|
`foo ${false ? unknown : unknown}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"unknown": ast.Variable{
|
|
Value: UnknownValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
ast.TypeUnknown,
|
|
},
|
|
|
|
{
|
|
// condition can be unknown, and result is unknown
|
|
`foo ${unknown ? "baz" : "bar"}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"unknown": ast.Variable{
|
|
Value: UnknownValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
ast.TypeUnknown,
|
|
},
|
|
|
|
{
|
|
"foo ${-bar}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: 41,
|
|
Type: ast.TypeInt,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo -41",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${bar+1}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: 41,
|
|
Type: ast.TypeInt,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo 42",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${bar+1}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: "41",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo 42",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${bar+baz}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: "41",
|
|
Type: ast.TypeString,
|
|
},
|
|
"baz": ast.Variable{
|
|
Value: "1",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo 42",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${bar+baz}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: 0.001,
|
|
Type: ast.TypeFloat,
|
|
},
|
|
"baz": ast.Variable{
|
|
Value: "0.002",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo 0.003",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${bar+baz}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: UnknownValue,
|
|
Type: ast.TypeUnknown,
|
|
},
|
|
"baz": ast.Variable{
|
|
Value: 1,
|
|
Type: ast.TypeInt,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
UnknownValue,
|
|
ast.TypeUnknown,
|
|
},
|
|
|
|
{
|
|
"foo ${rand()}",
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"rand": ast.Function{
|
|
ReturnType: ast.TypeString,
|
|
Callback: func([]interface{}) (interface{}, error) {
|
|
return "42", nil
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo 42",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${rand("foo", "bar")}`,
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"rand": ast.Function{
|
|
ReturnType: ast.TypeString,
|
|
Variadic: true,
|
|
VariadicType: ast.TypeString,
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
var result string
|
|
for _, a := range args {
|
|
result += a.(string)
|
|
}
|
|
return result, nil
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo foobar",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`${foo["bar"]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"world",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`${foo[var.key]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
"var.key": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "bar",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"world",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`${foo[bar[var.keyint]]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "i dont exist",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "bar",
|
|
},
|
|
},
|
|
},
|
|
"var.keyint": ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 1,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"world",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`${foo["bar"]} ${bar[1]}`,
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 10,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 20,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"world 20",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${foo[0]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"hello",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${foo[bar]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 1,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"world",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${foo[bar[1]]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
"bar": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 1,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"hello",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"aaa ${foo} aaa",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 42,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"aaa 42 aaa",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"aaa ${foo[1]} aaa",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 42,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 24,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"aaa 24 aaa",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"aaa ${foo[1]} - ${foo[0]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 42,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 24,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"aaa 24 - 42",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${var.foo} ${var.foo[0]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
true,
|
|
nil,
|
|
ast.TypeInvalid,
|
|
},
|
|
|
|
{
|
|
"${var.foo[0]} ${var.foo[1]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "hello",
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "world",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"hello world",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${foo[1]} ${foo[0]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 42,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 24,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"24 42",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${foo[1-3]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 42,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 24,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
true,
|
|
nil,
|
|
ast.TypeInvalid,
|
|
},
|
|
|
|
{
|
|
"${foo[2]}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"foo": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 42,
|
|
},
|
|
ast.Variable{
|
|
Type: ast.TypeInt,
|
|
Value: 24,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
true,
|
|
nil,
|
|
ast.TypeInvalid,
|
|
},
|
|
|
|
// Testing implicit type conversions
|
|
|
|
{
|
|
"foo ${bar}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: 42,
|
|
Type: ast.TypeInt,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo 42",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
`foo ${foo("42")}`,
|
|
&ast.BasicScope{
|
|
FuncMap: map[string]ast.Function{
|
|
"foo": 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
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo 42",
|
|
ast.TypeString,
|
|
},
|
|
|
|
// Multiline
|
|
{
|
|
"foo ${42+\n1.0}",
|
|
nil,
|
|
false,
|
|
"foo 43",
|
|
ast.TypeString,
|
|
},
|
|
|
|
// String vars should be able to implictly convert to floats
|
|
{
|
|
"${1.5 * var.foo}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Value: "42",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"63",
|
|
ast.TypeString,
|
|
},
|
|
|
|
// Unary
|
|
{
|
|
"foo ${-46}",
|
|
nil,
|
|
false,
|
|
"foo -46",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${-46 + 5}",
|
|
nil,
|
|
false,
|
|
"foo -41",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${46 + -5}",
|
|
nil,
|
|
false,
|
|
"foo 41",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${-bar}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: 41,
|
|
Type: ast.TypeInt,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo -41",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"foo ${5 + -bar}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: 41,
|
|
Type: ast.TypeInt,
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"foo -36",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${var.foo > 1 ? 5 : 0}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "3",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"5",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${var.foo > 1.5 ? 5 : 0}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "3",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"5",
|
|
ast.TypeString,
|
|
},
|
|
|
|
{
|
|
"${var.foo > 1.5 ? 5 : 0}",
|
|
&ast.BasicScope{
|
|
VarMap: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "1.2",
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
"0",
|
|
ast.TypeString,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
t.Run(fmt.Sprintf("%d-%s", i, tc.Input), func(t *testing.T) {
|
|
node, err := Parse(tc.Input)
|
|
if err != nil {
|
|
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
|
|
}
|
|
|
|
out, outType, err := internalEval(node, &EvalConfig{GlobalScope: tc.Scope})
|
|
if err != nil != tc.Error {
|
|
t.Fatalf("Error: %s\nInput: %s", err, tc.Input)
|
|
}
|
|
if tc.ResultType != ast.TypeInvalid && outType != tc.ResultType {
|
|
t.Fatalf("Wrong result type\nInput: %s\nGot: %#s\nWant: %s", tc.Input, outType, tc.ResultType)
|
|
}
|
|
if !reflect.DeepEqual(out, tc.Result) {
|
|
t.Fatalf("Wrong result value\nInput: %s\nGot: %#s\nWant: %s", tc.Input, out, tc.Result)
|
|
}
|
|
})
|
|
}
|
|
}
|