Skip to content

Commit

Permalink
Merge pull request #31 from bytedance/support_foreach_callback
Browse files Browse the repository at this point in the history
feature: support foreach items record callback
  • Loading branch information
AoranAllen authored Jan 22, 2025
2 parents f387066 + 389ee2d commit 91acf59
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 39 deletions.
73 changes: 73 additions & 0 deletions internal/core/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,76 @@ func (a *arishemCondition) Operator() string {
func (a *arishemCondition) Error() error {
return a.error
}

type arishemForeachItem struct {
Index int
ItemVal interface{}
Pass bool
}

func (a *arishemForeachItem) Idx() int {
return a.Index
}

func (a *arishemForeachItem) ItemValue() interface{} {
return a.ItemVal
}

func (a *arishemForeachItem) Passed() bool {
return a.Pass
}

func newArishemForeachItem(idx int, itemVal interface{}, pass bool) typedef.ForeachItem {
return &arishemForeachItem{
Index: idx,
ItemVal: itemVal,
Pass: pass,
}
}

type arishemForeachCondition struct {
*arishemCondition
foreachOperator string
foreachLogic string
leftItems []typedef.ForeachItem
}

func newArishemForeachCondition(
passed bool,
left interface{},
leftExpr string,
right interface{},
rightExpr string,
operator string,
error error,
foreachOperator string,
foreachLogic string,
foreachItems []typedef.ForeachItem,
) typedef.JudgeNode {
return &arishemForeachCondition{
arishemCondition: &arishemCondition{
passed: passed,
left: left,
leftExpr: leftExpr,
right: right,
rightExpr: rightExpr,
operator: operator,
error: error,
},
foreachOperator: foreachOperator,
foreachLogic: foreachLogic,
leftItems: foreachItems,
}
}

func (a *arishemForeachCondition) ForeachLogic() string {
return a.foreachLogic
}

func (a *arishemForeachCondition) ForeachOperator() string {
return a.foreachOperator
}

func (a *arishemForeachCondition) ForeachItems() []typedef.ForeachItem {
return a.leftItems
}
80 changes: 47 additions & 33 deletions internal/core/rule_visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"strings"
)

const foreachKey = "FOREACH"

type simpleVisitTarget struct {
name string
}
Expand Down Expand Up @@ -242,48 +244,29 @@ func (a *arishemRuleVisitor) VisitFullCondition(ctx *parser.FullConditionContext
left := a.Visit(lhsExpr)
right := a.Visit(rhsExpr)

pass := false
var pass = false
var err error

const foreachKey = "FOREACH"
var cdtInfo typedef.JudgeNode

if !strings.HasPrefix(opr, foreachKey) {
pass, err = evaluateLR(a, left, right, opr)
cdtInfo = newArishemCondition(pass, left, lhsExpr.GetText(), right, rhsExpr.GetText(), opr, err)
} else {
// execute the foreach operator
// check the left type whether array or not
var lArray []interface{}
lArray, err = tools.ConvToSliceUnifyType(left)
if err == nil {
// parse the operator, arishem grammar guarantee that here are at least two space
const space = " "
firstSpace := strings.Index(opr, space)
lastSpace := strings.LastIndex(opr, space)
foreachOpr := opr[firstSpace+1 : lastSpace]
if strings.HasPrefix(foreachOpr, foreachKey) {
err = errors.New("nested foreach operation is not supported")
} else {
logicOpr := toStdLogic(opr[lastSpace+1:])
for _, lVal := range lArray {
pass, err = evaluateLR(a, lVal, right, foreachOpr)
if err != nil {
break
}
if logicOpr == logicAnd && pass == false {
break
}
if logicOpr == logicOr && pass == true {
break
}
}
}
}
// parse operator and logic
const space = " "
firstSpace := strings.Index(opr, space)
lastSpace := strings.LastIndex(opr, space)
foreachOpr := opr[firstSpace+1 : lastSpace]
logicOpr := toStdLogic(opr[lastSpace+1:])
// execute the foreach condition
var foreachItems []typedef.ForeachItem
foreachItems, pass, err = a.visitForeachConditionInner(left, right, foreachOpr, logicOpr)
cdtInfo = newArishemForeachCondition(pass, left, lhsExpr.GetText(), right, rhsExpr.GetText(), opr, err, foreachOpr, logicOpr, foreachItems)
}
if err != nil {
a.errorCallback(node, err.Error())
}
// put it into cache
cdtInfo := newArishemCondition(pass, left, lhsExpr.GetText(), right, rhsExpr.GetText(), opr, err)
a.dataCtx.Set(ctx.GetAltNumber(), cdtInfo)
// observers call back
for _, obsr := range a.observers {
Expand All @@ -292,6 +275,37 @@ func (a *arishemRuleVisitor) VisitFullCondition(ctx *parser.FullConditionContext
return pass
}

// execute the foreach operator judgement
func (a *arishemRuleVisitor) visitForeachConditionInner(left, right interface{}, foreachOpr, logicOpr string) ([]typedef.ForeachItem, bool, error) {
if strings.HasPrefix(foreachOpr, foreachKey) {
return nil, false, errors.New("nested foreach operation is not supported")
}
// check the left type whether array or not
lArray, err := tools.ConvToSliceUnifyType(left)
if err != nil {
return nil, false, errors.New("foreach operation is supported only when left is slice/array type")
}
// parse the operator, arishem grammar guarantee that here are at least two space
items := make([]typedef.ForeachItem, 0, len(lArray)/2+1)
pass := false
for i, lVal := range lArray {
pass, err = evaluateLR(a, lVal, right, foreachOpr)

items = append(items, newArishemForeachItem(i, lVal, pass))

if err != nil {
break
}
if logicOpr == logicAnd && pass == false {
break
}
if logicOpr == logicOr && pass == true {
break
}
}
return items, pass, err
}

func (a *arishemRuleVisitor) VisitNullCondition(_ *parser.NullConditionContext) interface{} {
return false
}
Expand Down Expand Up @@ -706,7 +720,7 @@ func evaluateLR(ori *arishemRuleVisitor, left, right interface{}, opr string) (b
return false, errors.New("sub condition right type is not sub condition expression")
}
pass, err = evaluateSubCond(ori, rSubCond.name, lData, rSubCond.tree)
// if err is not nil, pass will have to be false
// if err is not nil, Pass will have to be false
if err == nil && (strings.HasPrefix(opr, "!") || strings.HasPrefix(opr, "NOT")) {
// revert the judge result
pass = !pass
Expand Down
22 changes: 16 additions & 6 deletions internal/core/rule_visitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/bytedance/arishem/internal/parser"
"github.com/bytedance/arishem/typedef"
"github.com/bytedance/gopkg/util/logger"
"github.com/bytedance/sonic"
"github.com/stretchr/testify/assert"
"testing"
)
Expand Down Expand Up @@ -63,9 +64,18 @@ func (t *testArisVisitObserver) HashCode() string {

func (t *testArisVisitObserver) OnJudgeNodeVisitEnd(ctx context.Context, info typedef.JudgeNode, vt typedef.VisitTarget) {
if info != nil {
logger.Infof("left: %v, leftExpr: %v, right: %v, rightExpr: %v, operator: %v, target: %v, err: %v",
info.Left(), info.LeftExpr(), info.Right(), info.RightExpr(), info.Operator(), vt.Identifier(), info.Error(),
)
foreachNode, ok := info.(typedef.ForeachJudgeNode)
if !ok {
logger.Infof("left: %v, leftExpr: %v, right: %v, rightExpr: %v, operator: %v, target: %v, err: %v",
info.Left(), info.LeftExpr(), info.Right(), info.RightExpr(), info.Operator(), vt.Identifier(), info.Error(),
)
} else {
itemsStr, _ := sonic.MarshalString(foreachNode.ForeachItems())
logger.Infof("[ForeachJudgeNode] left: %v, leftExpr: %v, right: %v, rightExpr: %v, operator: %v, target: %v, err: %v, foreachOperator: %v, foreachLogic: %v, foreachItems: %v",
foreachNode.Left(), info.LeftExpr(), foreachNode.Right(), info.RightExpr(), foreachNode.Operator(), vt.Identifier(), foreachNode.Error(),
foreachNode.ForeachOperator(), foreachNode.ForeachLogic(), itemsStr,
)
}
}
}

Expand Down Expand Up @@ -216,15 +226,15 @@ func TestConditionVisit(t *testing.T) {
assert.Empty(t, errMsg)
},
}, {
"CONDITION: [pass] logic math operator by LrMath",
"CONDITION: [Pass] logic math operator by LrMath",
`{"is_latest": true}`,
`{"OpLogic":"AND","Conditions":[{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":"==","Lhs":{"Const":{"NumConst":1.1232}},"Rhs":{"Const":{"NumConst":1.1232}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":"!=","Lhs":{"Const":{"NumConst":10}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":">","Lhs":{"Const":{"NumConst":10}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":"<","Lhs":{"Const":{"NumConst":-1}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":">=","Lhs":{"Const":{"NumConst":8}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":"<=","Lhs":{"Const":{"NumConst":8}},"Rhs":{"Const":{"NumConst":8}}}}}]}`,
func(pass bool, errMsg []string) {
assert.True(t, pass)
assert.Empty(t, errMsg)
},
}, {
"CONDITION: [not pass] logic math operator by LrMath",
"CONDITION: [not Pass] logic math operator by LrMath",
`{"is_latest": true}`,
`{"OpLogic":"AND","Conditions":[{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":"!=","Lhs":{"Const":{"NumConst":8}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":"!=","Lhs":{"Const":{"NumConst":10}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":">","Lhs":{"Const":{"NumConst":10}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":"<","Lhs":{"Const":{"NumConst":-1}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":">=","Lhs":{"Const":{"NumConst":8}},"Rhs":{"Const":{"NumConst":8}}}}},{"Operator":"==","Lhs":{"VarExpr":"is_latest"},"Rhs":{"MathExpr":{"OpMath":"<=","Lhs":{"Const":{"NumConst":8}},"Rhs":{"Const":{"NumConst":8}}}}}]}`,
func(pass bool, errMsg []string) {
Expand Down Expand Up @@ -484,7 +494,7 @@ func TestForeach(t *testing.T) {
}, {
"SUB CONDITION: judge with FOREACH VALID sub condition operator: PASS",
`{"item_info":{"item_list":[{"name":"name@1","price":100},{"name":"name@2","price":102.13},{"name":"name@3","price":200},{"name":"name@4","price":100},{"name":"name@5","price":101},{"name":"name@6","price":303.1234}]}}`,
`{"OpLogic":"&&","Conditions":[{"Operator":"FOREACH SUB_COND &&","Lhs":{"VarExpr":"item_info.item_list"},"Rhs":{"SubCondExpr":{"CondName":"MyCond"}}}]}`,
`{"OpLogic":"&&","Conditions":[{"Operator":"FOREACH SUB_COND ||","Lhs":{"VarExpr":"item_info.item_list"},"Rhs":{"SubCondExpr":{"CondName":"MyCond"}}}]}`,
func(_ string) (antlr.ParseTree, error) {
return parser.ParseArishemCondition(`{"OpLogic":"&&","Conditions":[{"Operator":">","Lhs":{"VarExpr":"price"},"Rhs":{"Const":{"NumConst":10}}},{"Operator":"STRING_CONTAINS","Lhs":{"VarExpr":"name"},"Rhs":{"Const":{"StrConst":"name@"}}}]}`)
},
Expand Down
13 changes: 13 additions & 0 deletions typedef/visit.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ type JudgeNode interface {
Error() error
}

type ForeachItem interface {
Idx() int
ItemValue() interface{}
Passed() bool
}

type ForeachJudgeNode interface {
JudgeNode
ForeachOperator() string
ForeachLogic() string
ForeachItems() []ForeachItem
}

// ActionAim is the interface that defines the aim of action.
type ActionAim interface {
ActionName() string
Expand Down

0 comments on commit 91acf59

Please sign in to comment.