diff --git a/arishem/arishem_lw_api.go b/arishem/arishem_lw_api.go index cade882..40fc0be 100644 --- a/arishem/arishem_lw_api.go +++ b/arishem/arishem_lw_api.go @@ -19,6 +19,7 @@ package arishem import ( "context" "errors" + "github.com/antlr/antlr4/runtime/Go/antlr/v4" "github.com/bytedance/arishem/internal/core" "github.com/bytedance/arishem/tools" "github.com/bytedance/arishem/typedef" @@ -41,31 +42,65 @@ func DataContext(ctx context.Context, json string) (dc typedef.DataCtx, err erro // ParseCondition will parse a condition string and return the rule tree with feature parameter pre-parsed. func ParseCondition(condition string) (tree *RuleTree, err error) { - condition = strings.TrimSpace(condition) - if condition == "" { - err = errors.New("empty cond text") + return ParseRuleTree(condition, ExprTypeCondition) +} + +// ParseAim will parse an aim string and return the rule tree with feature parameter pre-parsed. +func ParseAim(aim string) (tree *RuleTree, err error) { + return ParseRuleTree(aim, ExprTypeAim) +} + +// ParseRuleTree will try parsing expr to antlr parse tree, err will not be null when failed. +func ParseRuleTree(expr string, exprType ExprType) (exprTree *RuleTree, err error) { + expr = strings.TrimSpace(expr) + if expr == "" { + err = errors.New("empty expression") return } - condition, err = tools.Compat(condition) + expr, err = tools.Compat(expr) if err != nil { return } var ok bool - tree, ok = arishemConfiguration.TCache.Get(condition) - if ok && tree != nil { + exprTree, ok = arishemConfiguration.TCache.Get(expr) + if ok && exprTree != nil { return } - tree, err = parseRuleTree(condition, exprTypeCondition) + exprTree, err = parseRuleTree(expr, exprType) if err != nil { return } - if tree.Tree == nil { + if exprTree.Tree == nil { err = errors.New("tree parsed is null") return } return } +// WalkAim will try to parse the aimExpr into the antlr parse tree and visit it to get the result, err will not be null when parse aim expression failed. +func WalkAim(aimExpr string, dc typedef.DataCtx, opts ...ExecuteOption) (aim typedef.Aim, err error) { + var tree *RuleTree + tree, err = ParseAim(aimExpr) + if err != nil { + return + } + rv := core.NewArishemRuleVisitor() + ApplyExecuteOptions(rv, dc, opts...) + if len(tree.FeatParams) > 0 { + dc.PrefetchFeatures(tree.FeatParams) + } + aim = rv.VisitAim(tree.Tree, dc, &dummyVisitTarget{name: aimExpr}) + return +} + +// WalkAimTree will visit the aimTree to get the result +func WalkAimTree(aimTree antlr.ParseTree, dc typedef.DataCtx, opts ...ExecuteOption) (aim typedef.Aim) { + rv := core.NewArishemRuleVisitor() + ApplyExecuteOptions(rv, dc, opts...) + aim = rv.VisitAim(aimTree, dc, &dummyVisitTarget{name: aimTree.GetText()}) + return +} + // JudgeCondition will judge the condition string, pass will be set true if condition passed. func JudgeCondition(ctx context.Context, condition string, opts ...ExecuteOption) (pass bool, err error) { return JudgeConditionWithFactMeta(ctx, condition, "{}", opts...) diff --git a/arishem/arishem_lw_api_test.go b/arishem/arishem_lw_api_test.go index 6f008fd..2f06172 100644 --- a/arishem/arishem_lw_api_test.go +++ b/arishem/arishem_lw_api_test.go @@ -297,3 +297,51 @@ func TestLWAPI(t *testing.T) { case5() case6() } + +func TestWalkAim(t *testing.T) { + testCases := []*struct { + aimExpr string + dcExpr string + check func(aim typedef.Aim, err error) + }{ + { + `{ + "Const": { + "StrConst": "p-rule3 passed!" + } +}`, + `{}`, + func(aim typedef.Aim, err error) { + fmt.Printf("%v\n", aim.AsExpr()) + assert.NotNil(t, aim) + assert.Nil(t, err) + }, + }, + { + `{"MathExpr":{"OpMath":">","ParamList":[{"Const":{"NumConst":100}},{"Const":{"NumConst":20}}]}}`, + `{}`, + func(aim typedef.Aim, err error) { + assert.Nil(t, aim) + assert.NotNil(t, err) + fmt.Printf("%v\n", err) + }, + }, + { + `{"MapExpr":{"UserName":{"VarExpr":"user.username"},"UserAge":{"MathExpr":{"OpMath":"+","Lhs":{"Const":{"NumConst":10}},"Rhs":{"Const":{"NumConst":8}}}},"UserWeight":{"MathExpr":{"OpMath":"+","ParamList":[{"Const":{"NumConst":100}},{"Const":{"NumConst":20}}]}},"UserPersonality":{"FeatureExpr":{"FeaturePath":"user_center.user_personality","BuiltinParam":{"user_id":{"VarExpr":"user.id"}}}}}}`, + `{}`, + func(aim typedef.Aim, err error) { + assert.Nil(t, err) + assert.NotNil(t, aim) + fmt.Printf("%v\n", aim.AsExpr()) + }, + }, + } + for _, testCase := range testCases { + dc, err := DataContext(context.Background(), testCase.dcExpr) + if err != nil { + testCase.check(nil, err) + continue + } + testCase.check(WalkAim(testCase.aimExpr, dc)) + } +} diff --git a/arishem/arishem_rule.go b/arishem/arishem_rule.go index 41430a1..b7ee8c3 100644 --- a/arishem/arishem_rule.go +++ b/arishem/arishem_rule.go @@ -45,11 +45,11 @@ type RuleResult interface { Aim() typedef.Aim } -type exprType int +type ExprType int const ( - exprTypeCondition = iota - exprTypeAim + ExprTypeCondition = iota + ExprTypeAim ) type ruleResult struct { @@ -183,7 +183,7 @@ func tryParse(cdtExpr, aimExpr string) (cdtTree, aimTree *RuleTree, err error) { wg.Add(1) arishemConfiguration.RuleComputePool.Submit(func(interface{}) { defer wg.Done() - cdtTree, cdtErr = parseRuleTree(cdtExpr, exprTypeCondition) + cdtTree, cdtErr = parseRuleTree(cdtExpr, ExprTypeCondition) }, nil) } @@ -197,7 +197,7 @@ func tryParse(cdtExpr, aimExpr string) (cdtTree, aimTree *RuleTree, err error) { wg.Add(1) arishemConfiguration.RuleComputePool.Submit(func(interface{}) { defer wg.Done() - aimTree, aimErr = parseRuleTree(aimExpr, exprTypeAim) + aimTree, aimErr = parseRuleTree(aimExpr, ExprTypeAim) }, nil) } // wait for parsing @@ -223,11 +223,11 @@ func tryParse(cdtExpr, aimExpr string) (cdtTree, aimTree *RuleTree, err error) { return } -func parseRuleTree(exprStr string, expT exprType) (exprTree *RuleTree, err error) { +func parseRuleTree(exprStr string, expT ExprType) (exprTree *RuleTree, err error) { fppl := core.NewFeaturePreParseListener() var treeCtx antlr.ParseTree - if expT == exprTypeCondition { + if expT == ExprTypeCondition { treeCtx, err = parser.ParseArishemCondition(exprStr, fppl) } else { treeCtx, err = parser.ParseArishemAim(exprStr, fppl) diff --git a/internal/core/rule_visitor.go b/internal/core/rule_visitor.go index cbf7a01..e11be8a 100644 --- a/internal/core/rule_visitor.go +++ b/internal/core/rule_visitor.go @@ -99,7 +99,7 @@ func (a *arishemRuleVisitor) VisitAim(aimCtx antlr.ParseTree, dCtx typedef.DataC default: } if !flag { - a.errorCallback(node, "invalid condition context type") + a.errorCallback(node, "invalid aim context type") return } // assignment data context and visit target