Skip to content

Commit

Permalink
Merge branch 'master' into allow-operator-token-as-func
Browse files Browse the repository at this point in the history
  • Loading branch information
ckganesan authored Feb 27, 2024
2 parents 368bb6d + 2347a4e commit ad97ba3
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 30 deletions.
51 changes: 33 additions & 18 deletions builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,38 +394,53 @@ var Builtins = []*Function{
Name: "max",
Func: Max,
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) == 0 {
switch len(args) {
case 0:
return anyType, fmt.Errorf("not enough arguments to call max")
}
for _, arg := range args {
switch kind(arg) {
case reflect.Interface:
case 1:
if kindName := kind(args[0]); kindName == reflect.Array || kindName == reflect.Slice {
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for max (type %s)", arg)
}
fallthrough
default:
for _, arg := range args {
switch kind(arg) {
case reflect.Interface, reflect.Array, reflect.Slice:
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for max (type %s)", arg)
}
}
return args[0], nil
}
return args[0], nil
},
},
{
Name: "min",
Func: Min,
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) == 0 {
switch len(args) {
case 0:
return anyType, fmt.Errorf("not enough arguments to call min")
}
for _, arg := range args {
switch kind(arg) {
case reflect.Interface:
case 1:
if kindName := kind(args[0]); kindName == reflect.Array || kindName == reflect.Slice {
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for min (type %s)", arg)
}
fallthrough
default:
for _, arg := range args {
switch kind(arg) {
case reflect.Interface, reflect.Array, reflect.Slice:
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for min (type %s)", arg)
}
}
return args[0], nil

}
return args[0], nil
},
},
{
Expand Down
8 changes: 8 additions & 0 deletions builtin/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,14 @@ func TestBuiltin(t *testing.T) {
{`hasSuffix("foo,bar,baz", "baz")`, true},
{`max(1, 2, 3)`, 3},
{`max(1.5, 2.5, 3.5)`, 3.5},
{`max([1, 2, 3])`, 3},
{`max([1.5, 2.5, 3.5])`, 3.5},
{`max([1, 2, 4, 10], 20, [29, 23, -19])`, 29},
{`min([1, 2, 4, 10], 20, [29, 23, -19])`, -19},
{`min(1, 2, 3)`, 1},
{`min(1.5, 2.5, 3.5)`, 1.5},
{`min([1, 2, 3])`, 1},
{`min([1.5, 2.5, 3.5])`, 1.5},
{`sum(1..9)`, 45},
{`sum([.5, 1.5, 2.5])`, 4.5},
{`sum([])`, 0},
Expand Down Expand Up @@ -197,8 +203,10 @@ func TestBuiltin_errors(t *testing.T) {
{`trim()`, `not enough arguments to call trim`},
{`max()`, `not enough arguments to call max`},
{`max(1, "2")`, `invalid argument for max (type string)`},
{`max([1, "2"])`, `invalid argument for max (type string)`},
{`min()`, `not enough arguments to call min`},
{`min(1, "2")`, `invalid argument for min (type string)`},
{`min([1, "2"])`, `invalid argument for min (type string)`},
{`duration("error")`, `invalid duration`},
{`date("error")`, `invalid date`},
{`get()`, `invalid number of arguments (expected 2, got 0)`},
Expand Down
45 changes: 34 additions & 11 deletions builtin/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,21 +255,44 @@ func String(arg any) any {
}

func Max(args ...any) (any, error) {
var max any
for _, arg := range args {
if max == nil || runtime.Less(max, arg) {
max = arg
}
}
return max, nil
return minMaxFunc("max", runtime.Less, args)
}

func Min(args ...any) (any, error) {
var min any
return minMaxFunc("min", runtime.More, args)
}

func minMaxFunc(name string, fn func(any, any) bool, args []any) (any, error) {
var val any
for _, arg := range args {
if min == nil || runtime.More(min, arg) {
min = arg
switch v := arg.(type) {
case []float32, []float64, []uint, []uint8, []uint16, []uint32, []uint64, []int, []int8, []int16, []int32, []int64:
rv := reflect.ValueOf(v)
if rv.Len() == 0 {
return nil, fmt.Errorf("not enough arguments to call %s", name)
}
arg = rv.Index(0).Interface()
for i := 1; i < rv.Len(); i++ {
elem := rv.Index(i).Interface()
if fn(arg, elem) {
arg = elem
}
}
case []any:
var err error
if arg, err = minMaxFunc(name, fn, v); err != nil {
return nil, err
}
case float32, float64, uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
default:
if len(args) == 1 {
return arg, nil
}
return nil, fmt.Errorf("invalid argument for %s (type %T)", name, v)
}
if val == nil || fn(val, arg) {
val = arg
}
}
return min, nil
return val, nil
}
36 changes: 36 additions & 0 deletions expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,22 @@ func TestExpr(t *testing.T) {
`len({a: 1, b: 2, c: 2})`,
3,
},
{
`max([1, 2, 3])`,
3,
},
{
`max(1, 2, 3)`,
3,
},
{
`min([1, 2, 3])`,
1,
},
{
`min(1, 2, 3)`,
1,
},
{
`{foo: 0, bar: 1}`,
map[string]any{"foo": 0, "bar": 1},
Expand Down Expand Up @@ -1259,6 +1275,26 @@ func TestExpr(t *testing.T) {
},
{
`endsWith("foobar", "bar") == ("foobar" endsWith "bar")`,
true,
},
{
`1 > 2 < 3`,
false,
},
{
`1 < 2 < 3`,
true,
},
{
`1 < 2 < 3 > 4`,
false,
},
{
`1 < 2 < 3 > 2`,
true,
},
{
`1 < 2 < 3 == true`,
true,
},
}
Expand Down
4 changes: 4 additions & 0 deletions parser/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ var Binary = map[string]Operator{
"^": {100, Right},
"??": {500, Left},
}

func IsComparison(op string) bool {
return op == "<" || op == ">" || op == ">=" || op == "<="
}
36 changes: 36 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ func (p *parser) parseExpression(precedence int) Node {
break
}

if operator.IsComparison(opToken.Value) {
nodeLeft = p.parseComparison(nodeLeft, opToken, op.Precedence)
goto next
}

var nodeRight Node
if op.Associativity == operator.Left {
nodeRight = p.parseExpression(op.Precedence + 1)
Expand Down Expand Up @@ -711,3 +716,34 @@ func (p *parser) parsePostfixExpression(node Node) Node {
}
return node
}

func (p *parser) parseComparison(left Node, token Token, precedence int) Node {
var rootNode Node
for {
comparator := p.parseExpression(precedence + 1)
cmpNode := &BinaryNode{
Operator: token.Value,
Left: left,
Right: comparator,
}
cmpNode.SetLocation(token.Location)
if rootNode == nil {
rootNode = cmpNode
} else {
rootNode = &BinaryNode{
Operator: "&&",
Left: rootNode,
Right: cmpNode,
}
rootNode.SetLocation(token.Location)
}

left = comparator
token = p.current
if !(token.Is(Operator) && operator.IsComparison(token.Value) && p.err == nil) {
break
}
p.next()
}
return rootNode
}
60 changes: 60 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,66 @@ world`},
Left: &IdentifierNode{Value: "foo"},
Right: &StringNode{Value: "foo"}},
Right: &BoolNode{Value: false},
},
},
{
`1 < 2 > 3`,
&BinaryNode{
Operator: "&&",
Left: &BinaryNode{
Operator: "<",
Left: &IntegerNode{Value: 1},
Right: &IntegerNode{Value: 2},
},
Right: &BinaryNode{
Operator: ">",
Left: &IntegerNode{Value: 2},
Right: &IntegerNode{Value: 3},
},
},
},
{
`1 < 2 < 3 < 4`,
&BinaryNode{
Operator: "&&",
Left: &BinaryNode{
Operator: "&&",
Left: &BinaryNode{
Operator: "<",
Left: &IntegerNode{Value: 1},
Right: &IntegerNode{Value: 2},
},
Right: &BinaryNode{
Operator: "<",
Left: &IntegerNode{Value: 2},
Right: &IntegerNode{Value: 3},
},
},
Right: &BinaryNode{
Operator: "<",
Left: &IntegerNode{Value: 3},
Right: &IntegerNode{Value: 4},
},
},
},
{
`1 < 2 < 3 == true`,
&BinaryNode{
Operator: "==",
Left: &BinaryNode{
Operator: "&&",
Left: &BinaryNode{
Operator: "<",
Left: &IntegerNode{Value: 1},
Right: &IntegerNode{Value: 2},
},
Right: &BinaryNode{
Operator: "<",
Left: &IntegerNode{Value: 2},
Right: &IntegerNode{Value: 3},
},
},
Right: &BoolNode{Value: true},
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion testdata/examples.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13822,7 +13822,7 @@ min(ok ? array : false)
min(ok ? f64 : i)
min(ok ? half : ok)
min(ok ? i : f32)
min(ok ? list : score)
min(ok ? array : score)
min(ok ? true : div)
min(reduce(array, #))
min(reduce(array, 0.5))
Expand Down

0 comments on commit ad97ba3

Please sign in to comment.