Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bbq/vm/value_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ func init() {
interpreter.NativeArrayMapFunction,
),
)

registerBuiltinTypeBoundFunction(
typeQualifier,
NewNativeFunctionValueWithDerivedType(
sema.ArrayTypeReduceFunctionName,
func(receiver Value, context interpreter.ValueStaticTypeContext) *sema.FunctionType {
elementType := arrayElementTypeFromValue(receiver, context)
return sema.ArrayReduceFunctionType(elementType)
},
interpreter.NativeArrayReduceFunction,
),
)
}

// Functions available only for variable-sized arrays.
Expand Down
161 changes: 161 additions & 0 deletions interpreter/misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11281,6 +11281,167 @@ func TestInterpretArrayMap(t *testing.T) {
})
}

func TestInterpretArrayReduce(t *testing.T) {
t.Parallel()

t.Run("with variable sized array - sum", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs = [1, 2, 3, 4, 5]

let sum =
fun (acc: Int, x: Int): Int {
return acc + x
}

fun reduce(): Int {
return xs.reduce(initial: 0, sum)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredIntValueFromInt64(15),
val,
)
})

t.Run("with variable sized array - product", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs = [1, 2, 3, 4]

let product =
fun (acc: Int, x: Int): Int {
return acc * x
}

fun reduce(): Int {
return xs.reduce(initial: 1, product)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredIntValueFromInt64(24),
val,
)
})

t.Run("with empty array", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs: [Int] = []

let sum =
fun (acc: Int, x: Int): Int {
return acc + x
}

fun reduce(): Int {
return xs.reduce(initial: 42, sum)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredIntValueFromInt64(42),
val,
)
})

t.Run("with fixed sized array", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs: [Int; 4] = [10, 20, 30, 40]

let sum =
fun (acc: Int, x: Int): Int {
return acc + x
}

fun reduce(): Int {
return xs.reduce(initial: 0, sum)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredIntValueFromInt64(100),
val,
)
})

t.Run("with type conversion", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs = [1, 2, 3]

let concat =
fun (acc: String, x: Int): String {
return acc.concat(x.toString())
}

fun reduce(): String {
return xs.reduce(initial: "", concat)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredStringValue("123"),
val,
)
})

t.Run("mutation during reduce", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs = [1, 2, 3]
let xRef = &xs as auth(Mutate) &[Int]
let sum =
fun (acc: Int, x: Int): Int {
xRef.remove(at: 0)
return acc + x
}

fun reduce(): Int {
return xRef.reduce(initial: 0, sum)
}
`)

_, err := inter.Invoke("reduce")
var containerMutationError *interpreter.ContainerMutatedDuringIterationError
require.ErrorAs(t, err, &containerMutationError)
})
}

func TestInterpretArrayToVariableSized(t *testing.T) {
t.Parallel()

Expand Down
67 changes: 67 additions & 0 deletions interpreter/value_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,16 @@ func (v *ArrayValue) GetMethod(context MemberAccessibleContext, name string) Fun
NativeArrayMapFunction,
)

case sema.ArrayTypeReduceFunctionName:
return NewBoundHostFunctionValue(
context,
v,
sema.ArrayReduceFunctionType(
v.SemaType(context).ElementType(false),
),
NativeArrayReduceFunction,
)

case sema.ArrayTypeToVariableSizedFunctionName:
return NewBoundHostFunctionValue(
context,
Expand Down Expand Up @@ -1696,6 +1706,48 @@ func (v *ArrayValue) Map(
)
}

func (v *ArrayValue) Reduce(
context InvocationContext,
initial Value,
reducer FunctionValue,
) Value {

elementType := v.SemaType(context).ElementType(false)

reducerFunctionType := reducer.FunctionType(context)
parameterTypes := reducerFunctionType.ParameterTypes()
returnType := reducerFunctionType.ReturnTypeAnnotation.Type

accumulator := initial

v.Iterate(
context,
func(element Value) (resume bool) {

// Meter computation for iterating the array.
common.UseComputation(
context,
common.LoopComputationUsage,
)

accumulator = invokeFunctionValue(
context,
reducer,
[]Value{accumulator, element},
[]sema.Type{returnType, elementType},
parameterTypes,
returnType,
nil,
)

return true
},
false,
)

return accumulator
}

func (v *ArrayValue) ForEach(
context IterableValueForeachContext,
_ sema.Type,
Expand Down Expand Up @@ -2049,6 +2101,21 @@ var NativeArrayMapFunction = NativeFunction(
},
)

var NativeArrayReduceFunction = NativeFunction(
func(
context NativeFunctionContext,
_ TypeArgumentsIterator,
receiver Value,
args []Value,
) Value {
thisArray := AssertValueOfType[*ArrayValue](receiver)
initial := args[0]
funcValue := AssertValueOfType[FunctionValue](args[1])

return thisArray.Reduce(context, initial, funcValue)
},
)

var NativeArrayToVariableSizedFunction = NativeFunction(
func(
context NativeFunctionContext,
Expand Down
Loading
Loading