Skip to content

Commit 12f7b7c

Browse files
committed
Add groupBy
1 parent e67d2ed commit 12f7b7c

File tree

9 files changed

+85
-4
lines changed

9 files changed

+85
-4
lines changed

builtin/builtin.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,4 +690,9 @@ var Builtins = []*ast.Function{
690690
return arrayType, nil
691691
},
692692
},
693+
{
694+
Name: "groupBy",
695+
Predicate: true,
696+
Types: types(new(func([]any, func(any) any) map[any][]any)),
697+
},
693698
}

checker/checker.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,24 @@ func (v *checker) BuiltinNode(node *ast.BuiltinNode) (reflect.Type, info) {
718718
return integerType, info{}
719719
}
720720
return v.error(node.Arguments[1], "predicate should has one input and one output param")
721+
722+
case "groupBy":
723+
collection, _ := v.visit(node.Arguments[0])
724+
if !isArray(collection) && !isAny(collection) {
725+
return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
726+
}
727+
728+
v.begin(collection)
729+
closure, _ := v.visit(node.Arguments[1])
730+
v.end()
731+
732+
if isFunc(closure) &&
733+
closure.NumOut() == 1 &&
734+
closure.NumIn() == 1 && isAny(closure.In(0)) {
735+
736+
return reflect.TypeOf(map[any][]any{}), info{}
737+
}
738+
return v.error(node.Arguments[1], "predicate should has one input and one output param")
721739
}
722740

723741
if id, ok := builtin.Index[node.Name]; ok {

compiler/compiler.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,16 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
814814
c.emit(OpEnd)
815815
return
816816

817+
case "groupBy":
818+
c.compile(node.Arguments[0])
819+
c.emit(OpBegin)
820+
c.emitLoop(func() {
821+
c.compile(node.Arguments[1])
822+
c.emit(OpGroupBy)
823+
})
824+
c.emit(OpGetGroupBy)
825+
c.emit(OpEnd)
826+
return
817827
}
818828

819829
if id, ok := builtin.Index[node.Name]; ok {

debug/debugger.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ func StartDebugger(program *Program, env interface{}) {
130130
keys = append(keys, pair{"Index", s.Index})
131131
keys = append(keys, pair{"Len", s.Len})
132132
keys = append(keys, pair{"Count", s.Count})
133+
if s.GroupBy != nil {
134+
keys = append(keys, pair{"GroupBy", s.GroupBy})
135+
}
133136
row := 0
134137
for _, pair := range keys {
135138
scope.SetCellSimple(row, 0, fmt.Sprintf("%v: ", pair.key))

expr_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,29 @@ func TestExpr(t *testing.T) {
10401040
`4/2 == 2`,
10411041
true,
10421042
},
1043+
{
1044+
`groupBy(1..9, # % 2)`,
1045+
map[any][]any{
1046+
0: {2, 4, 6, 8},
1047+
1: {1, 3, 5, 7, 9},
1048+
},
1049+
},
1050+
{
1051+
`groupBy(1..9, # % 2)[0]`,
1052+
[]any{2, 4, 6, 8},
1053+
},
1054+
{
1055+
`groupBy(1..3, # > 1)[true]`,
1056+
[]any{2, 3},
1057+
},
1058+
{
1059+
`groupBy(1..3, # > 1 ? nil : "")[nil]`,
1060+
[]any{2, 3},
1061+
},
1062+
{
1063+
`groupBy(ArrayOfFoo, .Value).foo`,
1064+
[]any{env.ArrayOfFoo[0]},
1065+
},
10431066
}
10441067

10451068
for _, tt := range tests {

parser/parser.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var predicates = map[string]struct {
2828
"findIndex": {2},
2929
"findLast": {2},
3030
"findLastIndex": {2},
31+
"groupBy": {2},
3132
}
3233

3334
type parser struct {

vm/opcodes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ const (
7272
OpSetIndex
7373
OpGetCount
7474
OpGetLen
75+
OpGetGroupBy
7576
OpPointer
7677
OpThrow
78+
OpGroupBy
7779
OpBegin
7880
OpEnd // This opcode must be at the end of this list.
7981
)

vm/program.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ func (program *Program) Opcodes(w io.Writer) {
289289
case OpGetLen:
290290
code("OpGetLen")
291291

292+
case OpGetGroupBy:
293+
code("OpGetGroupBy")
294+
292295
case OpPointer:
293296
code("OpPointer")
294297

@@ -298,6 +301,9 @@ func (program *Program) Opcodes(w io.Writer) {
298301
case OpBegin:
299302
code("OpBegin")
300303

304+
case OpGroupBy:
305+
code("OpGroupBy")
306+
301307
case OpEnd:
302308
code("OpEnd")
303309

vm/vm.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ type VM struct {
3939
}
4040

4141
type Scope struct {
42-
Array reflect.Value
43-
Index int
44-
Len int
45-
Count int
42+
Array reflect.Value
43+
Index int
44+
Len int
45+
Count int
46+
GroupBy map[any][]any
4647
}
4748

4849
func Debug() *VM {
@@ -461,13 +462,25 @@ func (vm *VM) Run(program *Program, env interface{}) (_ interface{}, err error)
461462
scope := vm.Scope()
462463
vm.push(scope.Len)
463464

465+
case OpGetGroupBy:
466+
vm.push(vm.Scope().GroupBy)
467+
464468
case OpPointer:
465469
scope := vm.Scope()
466470
vm.push(scope.Array.Index(scope.Index).Interface())
467471

468472
case OpThrow:
469473
panic(vm.pop().(error))
470474

475+
case OpGroupBy:
476+
scope := vm.Scope()
477+
if scope.GroupBy == nil {
478+
scope.GroupBy = make(map[any][]any)
479+
}
480+
it := scope.Array.Index(scope.Index).Interface()
481+
key := vm.pop()
482+
scope.GroupBy[key] = append(scope.GroupBy[key], it)
483+
471484
case OpBegin:
472485
a := vm.pop()
473486
array := reflect.ValueOf(a)

0 commit comments

Comments
 (0)