Skip to content

Commit

Permalink
make variadics require at least 1 item, as that's most often what we …
Browse files Browse the repository at this point in the history
…need
  • Loading branch information
xrstf committed Dec 5, 2023
1 parent 063a5f5 commit 8e932ed
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 108 deletions.
41 changes: 20 additions & 21 deletions pkg/builtin/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ func ifElseFunction(ctx types.Context, test bool, yes, no ast.Expression) (any,
return result, err
}

// (This signature ensures do is always called with 1 expression at least.)

func doFunction(ctx types.Context, arg ast.Expression, args ...ast.Expression) (any, error) {
tupleCtx, result, err := eval.EvalExpression(ctx, arg)
if err != nil {
return nil, fmt.Errorf("argument #0: %w", err)
}
// NB: Variadic functions always require at least 1 argument in Rudi to match.
func doFunction(ctx types.Context, args ...ast.Expression) (any, error) {
var (
tupleCtx = ctx
result any
err error
)

// do not use evalArgs(), as we want to inherit the context between expressions
for i, arg := range args {
for _, arg := range args {
tupleCtx, result, err = eval.EvalExpression(tupleCtx, arg)
if err != nil {
return nil, fmt.Errorf("argument #%d: %w", i+1, err)
return nil, err
}
}

Expand Down Expand Up @@ -121,28 +121,23 @@ func hasFunction(ctx types.Context, arg ast.Expression) (any, error) {
}

// (default TEST:Expression FALLBACK:any)
func defaultFunction(ctx types.Context, test ast.Expression, fallback ast.Expression) (any, error) {
_, result, err := eval.EvalExpression(ctx, test)
if err != nil {
return nil, fmt.Errorf("argument #0: %w", err)
}

// this function purposefully always uses humane coalescing for this check
boolified, err := coalescing.NewHumane().ToBool(result)
func defaultFunction(ctx types.Context, value any, fallback ast.Expression) (any, error) {
// this function purposefully always uses humane coalescing, but only for this check
boolified, err := coalescing.NewHumane().ToBool(value)
if err != nil {
return nil, fmt.Errorf("argument #0: %w", err)
}

if boolified {
return result, nil
return value, nil
}

_, result, err = eval.EvalExpression(ctx, fallback)
_, value, err = eval.EvalExpression(ctx, fallback)
if err != nil {
return nil, fmt.Errorf("argument #1: %w", err)
}

return result, nil
return value, nil
}

func tryFunction(ctx types.Context, test ast.Expression) (any, error) {
Expand Down Expand Up @@ -283,6 +278,10 @@ func isEmptyFunction(val bool) (any, error) {
return !val, nil
}

func errorFunction(ctx types.Context, format string, args ...any) (any, error) {
func errorFunction(message string) (any, error) {
return nil, errors.New(message)
}

func fmtErrorFunction(format string, args ...any) (any, error) {
return nil, fmt.Errorf(format, args...)
}
2 changes: 1 addition & 1 deletion pkg/builtin/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var (
"delete": functions.NewBuilder(deleteFunction).WithBangHandler(deleteBangHandler).WithDescription("removes a key from an object or an item from a vector").Build(),
"do": functions.NewBuilder(doFunction).WithDescription("eval a sequence of statements where only one expression is valid").Build(),
"empty?": functions.NewBuilder(isEmptyFunction).WithCoalescer(humaneCoalescer).WithDescription("returns true when the given value is empty-ish (0, false, null, \"\", ...)").Build(),
"error": functions.NewBuilder(errorFunction).WithDescription("returns an error").Build(),
"error": functions.NewBuilder(errorFunction, fmtErrorFunction).WithDescription("returns an error").Build(),
"has?": functions.NewBuilder(hasFunction).WithDescription("returns true if the given symbol's path expression points to an existing value").Build(),
"if": functions.NewBuilder(ifElseFunction, ifFunction).WithDescription("evaluate one of two expressions based on a condition").Build(),
"set": functions.NewBuilder(setFunction).WithDescription("set a value in a variable/document, only really useful with ! modifier (set!)").Build(),
Expand Down
8 changes: 8 additions & 0 deletions pkg/builtin/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func stringLenFunction(s string) (any, error) {
return len(s), nil
}

// (append VEC ITEMS+)
func appendToVectorFunction(base []any, args ...any) (any, error) {
result := []any{}
result = append(result, base...)
Expand All @@ -33,18 +34,22 @@ func appendToVectorFunction(base []any, args ...any) (any, error) {
return result, nil
}

// (append STR ITEMS+)
func appendToStringFunction(base string, args ...string) (any, error) {
return base + strings.Join(args, ""), nil
}

// (prepend VEC ITEMS+)
func prependToVectorFunction(base []any, args ...any) (any, error) {
return append(args, base...), nil
}

// (prepend STR ITEMS+)
func prependToStringFunction(base string, args ...string) (any, error) {
return strings.Join(args, "") + base, nil
}

// (reverse STR)
func reverseStringFunction(s string) (any, error) {
// thank you https://stackoverflow.com/a/10030772
result := []rune(s)
Expand All @@ -55,6 +60,7 @@ func reverseStringFunction(s string) (any, error) {
return string(result), nil
}

// (reverse VEC)
func reverseVectorFunction(vec []any) (any, error) {
// clone original data
result := append([]any{}, vec...)
Expand Down Expand Up @@ -409,10 +415,12 @@ func decodeNamingVector(expr ast.Expression) (indexName string, valueName string
return indexName, valueName, nil
}

// (contains? STR STR)
func stringContainsFunction(haystack string, needle string) (any, error) {
return strings.Contains(haystack, needle), nil
}

// (contains? VEC ITEM)
func vectorContainsFunction(ctx types.Context, haystack []any, needle any) (any, error) {
for _, val := range haystack {
equal, err := equality.Equal(ctx.Coalesce(), val, needle)
Expand Down
12 changes: 2 additions & 10 deletions pkg/builtin/logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ import (
"go.xrstf.de/rudi/pkg/lang/ast"
)

func andFunction(ctx types.Context, base bool, args ...ast.Expression) (any, error) {
if !base {
return false, nil
}

func andFunction(ctx types.Context, args ...ast.Expression) (any, error) {
for i, arg := range args {
_, evaluated, err := eval.EvalExpression(ctx, arg)
if err != nil {
Expand All @@ -35,11 +31,7 @@ func andFunction(ctx types.Context, base bool, args ...ast.Expression) (any, err
return true, nil
}

func orFunction(ctx types.Context, base bool, args ...ast.Expression) (any, error) {
if base {
return true, nil
}

func orFunction(ctx types.Context, args ...ast.Expression) (any, error) {
for i, arg := range args {
_, evaluated, err := eval.EvalExpression(ctx, arg)
if err != nil {
Expand Down
53 changes: 20 additions & 33 deletions pkg/builtin/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,84 +9,71 @@ import (
"go.xrstf.de/rudi/pkg/lang/ast"
)

func integerAddFunction(a, b int64, extra ...int64) (any, error) {
sum := a + b
func integerAddFunction(base int64, extra ...int64) (any, error) {
for _, num := range extra {
sum += num
base += num
}

return sum, nil
return base, nil
}

func numberAddFunction(a, b ast.Number, extra ...ast.Number) (any, error) {
sum := a.MustToFloat() + b.MustToFloat()
func numberAddFunction(base ast.Number, extra ...ast.Number) (any, error) {
sum := base.MustToFloat()
for _, num := range extra {
sum += num.MustToFloat()
}

return sum, nil
}

func integerSubFunction(a, b int64, extra ...int64) (any, error) {
diff := a - b
func integerSubFunction(base int64, extra ...int64) (any, error) {
for _, num := range extra {
diff -= num
base -= num
}

return diff, nil
return base, nil
}

func numberSubFunction(a, b ast.Number, extra ...ast.Number) (any, error) {
diff := a.MustToFloat() - b.MustToFloat()
func numberSubFunction(base ast.Number, extra ...ast.Number) (any, error) {
diff := base.MustToFloat()
for _, num := range extra {
diff -= num.MustToFloat()
}

return diff, nil
}

func integerMultFunction(a, b int64, extra ...int64) (any, error) {
product := a * b
func integerMultFunction(base int64, extra ...int64) (any, error) {
for _, num := range extra {
product *= num
base *= num
}

return product, nil
return base, nil
}

func numberMultFunction(a, b ast.Number, extra ...ast.Number) (any, error) {
product := a.MustToFloat() * b.MustToFloat()
func numberMultFunction(base ast.Number, extra ...ast.Number) (any, error) {
product := base.MustToFloat()
for _, num := range extra {
product *= num.MustToFloat()
}

return product, nil
}

func integerDivFunction(a, b int64, extra ...int64) (any, error) {
if b == 0 {
return nil, errors.New("division by zero")
}

result := a / b

func integerDivFunction(base int64, extra ...int64) (any, error) {
for _, num := range extra {
if num == 0 {
return nil, errors.New("division by zero")
}

result /= num
base /= num
}

return result, nil
return base, nil
}

func numberDivFunction(a, b ast.Number, extra ...ast.Number) (any, error) {
if b.MustToFloat() == 0 {
return nil, errors.New("division by zero")
}

result := a.MustToFloat() / b.MustToFloat()
func numberDivFunction(base ast.Number, extra ...ast.Number) (any, error) {
result := base.MustToFloat()

for _, num := range extra {
if num.MustToFloat() == 0 {
Expand Down
8 changes: 8 additions & 0 deletions pkg/eval/functions/args_consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,16 @@ func contextConsumer(ctx types.Context, args []cachedExpression) (asserted []any
return []any{ctx}, args, nil
}

// toVariadicConsumer wraps a singular consumer to consume all remaining args. In constrast to
// Go, variadic arguments must have at least 1 item (i.e. calling func(foo string, a ...int) with
// ("abc") only is invalid).
func toVariadicConsumer(singleConsumer argsConsumer) argsConsumer {
return func(ctx types.Context, args []cachedExpression) (asserted []any, remaining []cachedExpression, err error) {
// at least one argument is required, otherwise variadics do not match
if len(args) == 0 {
return nil, nil, nil
}

leftover := args
result := []any{}

Expand Down
30 changes: 30 additions & 0 deletions pkg/eval/functions/args_consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,33 @@ func TestExpressionConsumer(t *testing.T) {
})
}
}

func TestVariadicConsumer(t *testing.T) {
testcases := []argsConsumerTestcase{
{
name: "variadic request at least 1 item",
args: []ast.Expression{},
expected: nil,
remaining: 0,
},
{
name: "consume all strings",
args: []ast.Expression{
ast.String("foo"),
ast.Bool(true),
ast.Null{},
},
expected: []any{"foo", "true", ""},
remaining: 0,
},
}

coalescer := coalescing.NewHumane()
ctx := types.NewContext(types.Document{}, nil, nil, coalescer)

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tc.Test(t, ctx, toVariadicConsumer(stringConsumer))
})
}
}
22 changes: 12 additions & 10 deletions pkg/eval/functions/args_matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ func TestNewArgsMatcherSignatures(t *testing.T) {
fun: func(map[string][]any) (any, error) { panic("") },
invalid: true,
},
{
name: "accept only variadic arg",
fun: func(...int64) (any, error) { panic("") },
},
{
name: "accept variadic basic arg",
fun: func(string, []any, ...int64) (any, error) { panic("") },
Expand Down Expand Up @@ -332,11 +336,10 @@ func TestArgsMatcher(t *testing.T) {
expected: []any{ast.Shim{Value: 1}},
},
{
name: "basic variadic parameter with no args",
fun: func(...string) (any, error) { return nil, nil },
args: []ast.Expression{},
match: true,
expected: []any{},
name: "variadic parameters require at least 1 item",
fun: func(...string) (any, error) { return nil, nil },
args: []ast.Expression{},
match: false,
},
{
name: "basic variadic parameter",
Expand All @@ -353,11 +356,10 @@ func TestArgsMatcher(t *testing.T) {
expected: []any{"foo", "bar"},
},
{
name: "variadic can be empty",
fun: func(string, ...string) (any, error) { return nil, nil },
args: []ast.Expression{ast.String("foo")},
match: true,
expected: []any{"foo"},
name: "variadic cannot be empty",
fun: func(string, ...string) (any, error) { return nil, nil },
args: []ast.Expression{ast.String("foo")},
match: false,
},
{
name: "variadic slices",
Expand Down
Loading

0 comments on commit 8e932ed

Please sign in to comment.