refactor rule evaluator to make use of expr envs
This commit is contained in:
4
go.mod
4
go.mod
@@ -6,7 +6,7 @@ require (
|
||||
github.com/expr-lang/expr v1.16.5
|
||||
github.com/faiface/beep v1.1.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/sys v0.20.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -15,7 +15,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
|
||||
golang.org/x/image v0.15.0 // indirect
|
||||
golang.org/x/image v0.16.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
8
go.sum
8
go.sum
@@ -34,16 +34,16 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSin
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -55,7 +55,7 @@ func ParseNIPFile(filePath string) (Rules, error) {
|
||||
|
||||
for fileScanner.Scan() {
|
||||
lineNumber++
|
||||
rule, err := New(fileScanner.Text(), filePath, lineNumber)
|
||||
rule, err := NewRule(fileScanner.Text(), filePath, lineNumber)
|
||||
if errors.Is(err, ErrEmptyRule) {
|
||||
continue
|
||||
}
|
||||
|
||||
218
pkg/nip/rule.go
218
pkg/nip/rule.go
@@ -8,67 +8,42 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/vm"
|
||||
"github.com/hectorgimenez/d2go/pkg/data"
|
||||
"github.com/hectorgimenez/d2go/pkg/data/item"
|
||||
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
||||
)
|
||||
|
||||
var (
|
||||
fixedPropsRegexp = regexp.MustCompile(`(\[(type|quality|class|name|flag|color)]\s*(<=|<|>|>=|!=|==)\s*([a-zA-Z]+))`)
|
||||
statsRegexp = regexp.MustCompile(`\[(.*?)]`)
|
||||
maxQtyRegexp = regexp.MustCompile(`(\[maxquantity]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`)
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
RawLine string // Original line, don't use it for evaluation
|
||||
Stages [3]string
|
||||
Filename string
|
||||
LineNumber int
|
||||
Enabled bool
|
||||
maxQuantity int
|
||||
}
|
||||
|
||||
type Rules []Rule
|
||||
|
||||
type RuleResult int
|
||||
|
||||
var (
|
||||
const (
|
||||
RuleResultFullMatch RuleResult = 1
|
||||
RuleResultPartial RuleResult = 2
|
||||
RuleResultNoMatch RuleResult = 3
|
||||
)
|
||||
|
||||
func (r Rules) EvaluateAll(it data.Item) (Rule, RuleResult) {
|
||||
bestMatch := RuleResultNoMatch
|
||||
bestMatchingRule := Rule{}
|
||||
for _, rule := range r {
|
||||
if rule.Enabled {
|
||||
result, err := rule.Evaluate(it)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if result == RuleResultFullMatch {
|
||||
return rule, result
|
||||
}
|
||||
if result == RuleResultPartial {
|
||||
bestMatch = result
|
||||
bestMatchingRule = rule
|
||||
}
|
||||
}
|
||||
}
|
||||
var (
|
||||
fixedPropsRegexp = regexp.MustCompile(`(\[(type|quality|class|name|flag|color)]\s*(<=|<|>|>=|!=|==)\s*([a-zA-Z0-9]+))`)
|
||||
statsRegexp = regexp.MustCompile(`\[(.*?)]`)
|
||||
maxQtyRegexp = regexp.MustCompile(`(\[maxquantity]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`)
|
||||
)
|
||||
|
||||
return bestMatchingRule, bestMatch
|
||||
type Rule struct {
|
||||
RawLine string // Original line, don't use it for evaluation
|
||||
Filename string
|
||||
LineNumber int
|
||||
Enabled bool
|
||||
maxQuantity int
|
||||
stage1 *vm.Program
|
||||
stage2 *vm.Program
|
||||
requiredStats []string
|
||||
}
|
||||
|
||||
func New(rawRule string, filename string, lineNumber int) (Rule, error) {
|
||||
rule := sanitizeLine(rawRule)
|
||||
type RuleResult int
|
||||
type Rules []Rule
|
||||
|
||||
// Check for not supported stats
|
||||
for _, prop := range statsRegexp.FindAllStringSubmatch(rule, -1) {
|
||||
if slices.Contains(notSupportedStats, prop[1]) {
|
||||
return Rule{}, fmt.Errorf("property %s is not supported, please remove it", prop[1])
|
||||
}
|
||||
}
|
||||
var fixedPropsList = map[string]int{"type": 0, "quality": 0, "class": 0, "name": 0, "flag": 0, "color": 0}
|
||||
|
||||
func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
|
||||
rule := sanitizeLine(rawRule)
|
||||
|
||||
// Try to get the maxquantity value and purge it from the rule, we can not evaluate it
|
||||
maxQuantity := 0
|
||||
@@ -87,53 +62,84 @@ func New(rawRule string, filename string, lineNumber int) (Rule, error) {
|
||||
return Rule{}, ErrEmptyRule
|
||||
}
|
||||
|
||||
stages := [3]string{}
|
||||
for i, stg := range strings.Split(rule, "#") {
|
||||
stages[i] = strings.TrimSpace(stg)
|
||||
}
|
||||
|
||||
return Rule{
|
||||
r := Rule{
|
||||
RawLine: rawRule,
|
||||
Stages: stages,
|
||||
Filename: filename,
|
||||
LineNumber: lineNumber,
|
||||
Enabled: true,
|
||||
maxQuantity: maxQuantity,
|
||||
}, nil
|
||||
}
|
||||
|
||||
for i, stg := range strings.Split(rule, "#") {
|
||||
line := strings.TrimSpace(stg)
|
||||
if line == "" {
|
||||
break
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
line, err := replaceStringPropertiesInStage1(line)
|
||||
if err != nil {
|
||||
return Rule{}, err
|
||||
}
|
||||
|
||||
line = strings.ReplaceAll(line, "[", "")
|
||||
line = strings.ReplaceAll(line, "]", "")
|
||||
program, err := expr.Compile(line, expr.Env(fixedPropsList))
|
||||
if err != nil {
|
||||
return Rule{}, fmt.Errorf("error compiling rule: %w", err)
|
||||
}
|
||||
r.stage1 = program
|
||||
}
|
||||
|
||||
if i == 1 {
|
||||
r.requiredStats = getRequiredStatsForRule(line)
|
||||
|
||||
statsMap := make(map[string]int)
|
||||
for _, prop := range r.requiredStats {
|
||||
statsMap[prop] = 0
|
||||
// Check for not supported stats
|
||||
if slices.Contains(notSupportedStats, prop) {
|
||||
return Rule{}, fmt.Errorf("property %s is not supported, please remove it", prop)
|
||||
}
|
||||
}
|
||||
|
||||
line = strings.ReplaceAll(line, "[", "")
|
||||
line = strings.ReplaceAll(line, "]", "")
|
||||
program, err := expr.Compile(line, expr.Env(statsMap))
|
||||
if err != nil {
|
||||
return Rule{}, fmt.Errorf("error compiling rule: %w", err)
|
||||
}
|
||||
r.stage2 = program
|
||||
|
||||
// We only care about first and second part, third one is maxquantity and was already parsed
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
||||
stage1 := r.Stages[0]
|
||||
baseProperties := fixedPropsRegexp.FindAllStringSubmatch(stage1, -1)
|
||||
for _, prop := range baseProperties {
|
||||
switch prop[2] {
|
||||
stage1Props := make(map[string]int)
|
||||
for prop := range fixedPropsList {
|
||||
switch prop {
|
||||
case "type":
|
||||
partialReplace := strings.ReplaceAll(prop[0], fmt.Sprintf("[%s]", prop[2]), fmt.Sprintf("%d", typeAliases[it.TypeAsString()]))
|
||||
partialReplace = strings.ReplaceAll(partialReplace, prop[4], fmt.Sprintf("%d", typeAliases[prop[4]]))
|
||||
stage1 = strings.ReplaceAll(stage1, prop[0], partialReplace)
|
||||
stage1Props["type"] = typeAliases[it.TypeAsString()]
|
||||
case "quality":
|
||||
partialReplace := strings.ReplaceAll(prop[0], fmt.Sprintf("[%s]", prop[2]), fmt.Sprintf("%d", it.Quality))
|
||||
partialReplace = strings.ReplaceAll(partialReplace, prop[4], fmt.Sprintf("%d", qualityAliases[prop[4]]))
|
||||
stage1 = strings.ReplaceAll(stage1, prop[0], partialReplace)
|
||||
stage1Props["quality"] = int(it.Quality)
|
||||
case "class":
|
||||
partialReplace := strings.ReplaceAll(prop[0], fmt.Sprintf("[%s]", prop[2]), fmt.Sprintf("%d", it.Desc().Tier()))
|
||||
partialReplace = strings.ReplaceAll(partialReplace, prop[4], fmt.Sprintf("%d", classAliases[prop[4]]))
|
||||
stage1 = strings.ReplaceAll(stage1, prop[0], partialReplace)
|
||||
stage1Props["class"] = int(it.Desc().Tier())
|
||||
case "name":
|
||||
partialReplace := strings.ReplaceAll(prop[0], fmt.Sprintf("[%s]", prop[2]), fmt.Sprintf("%d", item.GetIDByName(string(it.Name))))
|
||||
partialReplace = strings.ReplaceAll(partialReplace, prop[4], fmt.Sprintf("%d", item.GetIDByName(prop[4])))
|
||||
stage1 = strings.ReplaceAll(stage1, prop[0], partialReplace)
|
||||
stage1Props["name"] = it.ID
|
||||
case "flag":
|
||||
partialReplace := strings.ReplaceAll(prop[0], fmt.Sprintf("[%s]", prop[2]), fmt.Sprintf("%d", map[bool]int{true: 1, false: 0}[it.Ethereal]))
|
||||
partialReplace = strings.ReplaceAll(partialReplace, prop[4], fmt.Sprintf("%d", 1))
|
||||
stage1 = strings.ReplaceAll(stage1, prop[0], partialReplace)
|
||||
stage1Props["flag"] = map[bool]int{true: 1, false: 0}[it.Ethereal]
|
||||
case "color":
|
||||
// TODO: Not supported yet
|
||||
}
|
||||
}
|
||||
|
||||
// Let's evaluate first stage
|
||||
stage1Result, err := expr.Eval(stage1, nil)
|
||||
stage1Result, err := expr.Run(r.stage1, stage1Props)
|
||||
if err != nil {
|
||||
return RuleResultNoMatch, fmt.Errorf("error evaluating rule: %w", err)
|
||||
}
|
||||
@@ -143,27 +149,25 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
||||
return RuleResultNoMatch, nil
|
||||
}
|
||||
|
||||
// Let's go with other stats now
|
||||
// TODO: properties are missing (enhanceddefense, enhanceddamage, etc)
|
||||
stage2 := r.Stages[1]
|
||||
stage2Result := true
|
||||
if stage2 != "" {
|
||||
for _, statName := range statsRegexp.FindAllStringSubmatch(stage2, -1) {
|
||||
statData, found := statAliases[statName[1]]
|
||||
if r.stage2 != nil {
|
||||
stage2Props := make(map[string]int)
|
||||
for _, statName := range r.requiredStats {
|
||||
statData, found := statAliases[statName]
|
||||
if !found {
|
||||
return RuleResultNoMatch, fmt.Errorf("property %s is not valid or not supported", statName[1])
|
||||
return RuleResultNoMatch, fmt.Errorf("property %s is not valid or not supported", statName)
|
||||
}
|
||||
|
||||
// Exception for enhanceddefense, is not accurate
|
||||
if statName[0] == "[enhanceddefense]" {
|
||||
if strings.EqualFold(statName, "enhanceddefense") {
|
||||
enhancedDefense := r.calculateEnhancedDefense(it)
|
||||
stage2 = strings.ReplaceAll(stage2, statName[0], fmt.Sprintf("%d", enhancedDefense))
|
||||
stage2Props[statName] = enhancedDefense
|
||||
continue
|
||||
}
|
||||
|
||||
if itemTypes, found := blockedStatsForItemType[statName[1]]; found {
|
||||
if itemTypes, found := blockedStatsForItemType[statName]; found {
|
||||
if slices.Contains(itemTypes, it.TypeAsString()) {
|
||||
return RuleResultNoMatch, fmt.Errorf("property %s is not supported for item type %s", statName[0], it.TypeAsString())
|
||||
return RuleResultNoMatch, fmt.Errorf("property %s is not supported for item type %s", statName, it.TypeAsString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,11 +176,10 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
||||
layer = statData[1]
|
||||
}
|
||||
itemStat, _ := it.FindStat(stat.ID(statData[0]), layer)
|
||||
// By default, value will be 0 is stat is not found, it's okay for evaluation purposes.
|
||||
stage2 = strings.ReplaceAll(stage2, statName[0], fmt.Sprintf("%d", itemStat.Value))
|
||||
stage2Props[statName] = itemStat.Value
|
||||
}
|
||||
|
||||
res, err := expr.Eval(stage2, nil)
|
||||
res, err := expr.Run(r.stage2, stage2Props)
|
||||
if err != nil {
|
||||
return RuleResultNoMatch, fmt.Errorf("error evaluating rule: %w", err)
|
||||
}
|
||||
@@ -192,10 +195,45 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
||||
if stage1Result.(bool) && !it.Identified {
|
||||
return RuleResultPartial, nil
|
||||
}
|
||||
|
||||
return RuleResultNoMatch, nil
|
||||
}
|
||||
|
||||
func replaceStringPropertiesInStage1(stage1 string) (string, error) {
|
||||
baseProperties := fixedPropsRegexp.FindAllStringSubmatch(stage1, -1)
|
||||
for _, prop := range baseProperties {
|
||||
replaceWith := ""
|
||||
switch prop[2] {
|
||||
case "type":
|
||||
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", typeAliases[prop[4]]))
|
||||
case "quality":
|
||||
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", qualityAliases[prop[4]]))
|
||||
case "class":
|
||||
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", classAliases[prop[4]]))
|
||||
case "name":
|
||||
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", item.GetIDByName(prop[4])))
|
||||
case "flag":
|
||||
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", 1))
|
||||
case "color":
|
||||
// TODO: Not supported yet
|
||||
return "", fmt.Errorf("property %s is not supported yet", prop[2])
|
||||
}
|
||||
|
||||
if replaceWith != "" {
|
||||
stage1 = strings.ReplaceAll(stage1, prop[0], replaceWith)
|
||||
}
|
||||
}
|
||||
|
||||
return stage1, nil
|
||||
}
|
||||
|
||||
func getRequiredStatsForRule(line string) []string {
|
||||
statsList := make([]string, 0)
|
||||
for _, statName := range statsRegexp.FindAllStringSubmatch(line, -1) {
|
||||
statsList = append(statsList, statName[1])
|
||||
}
|
||||
return statsList
|
||||
}
|
||||
|
||||
// MaxQuantity returns the maximum quantity of items that character can have, 0 means no limit
|
||||
func (r Rule) MaxQuantity() int {
|
||||
return r.maxQuantity
|
||||
|
||||
@@ -36,6 +36,7 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 603,
|
||||
Name: "SmAlLCharM",
|
||||
Quality: item.QualityMagic,
|
||||
Ethereal: true,
|
||||
@@ -57,6 +58,7 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Quality: item.QualitySuperior,
|
||||
Ethereal: false,
|
||||
@@ -76,6 +78,7 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Stats: []stat.Data{
|
||||
{ID: stat.AddClassSkills, Value: 3, Layer: 1},
|
||||
@@ -92,6 +95,7 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Stats: []stat.Data{
|
||||
{ID: stat.SingleSkill, Value: 3, Layer: 55},
|
||||
@@ -100,20 +104,6 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
},
|
||||
want: RuleResultFullMatch,
|
||||
},
|
||||
{
|
||||
name: "Ensure [color] returns error, not supported yet",
|
||||
fields: fields{
|
||||
RawLine: "[type] == armor && [color] == 1000 && [quality] == magic",
|
||||
Enabled: true,
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
Name: "mageplate",
|
||||
Quality: item.QualityMagic,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Unid item matching base stats should return partial match",
|
||||
fields: fields{
|
||||
@@ -122,6 +112,7 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Identified: false,
|
||||
Name: "mageplate",
|
||||
Quality: item.QualityMagic,
|
||||
@@ -137,6 +128,7 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Identified: false,
|
||||
Name: "mageplate",
|
||||
Quality: item.QualityMagic,
|
||||
@@ -174,7 +166,7 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
Identified: true,
|
||||
ID: 0,
|
||||
Name: "handaxe",
|
||||
Quality: item.QualityUnique,
|
||||
Quality: item.QualitySuperior,
|
||||
Stats: []stat.Data{},
|
||||
},
|
||||
},
|
||||
@@ -202,7 +194,7 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r, err := New(tt.fields.RawLine, tt.fields.Filename, tt.fields.LineNumber)
|
||||
r, err := NewRule(tt.fields.RawLine, tt.fields.Filename, tt.fields.LineNumber)
|
||||
require.NoError(t, err)
|
||||
got, err := r.Evaluate(tt.args.item)
|
||||
if !tt.wantErr {
|
||||
@@ -234,10 +226,17 @@ func TestNew(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Ensure [color] returns error, not supported yet",
|
||||
args: args{
|
||||
rawRule: "[type] == armor && [color] == 1000 && [quality] == magic",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := New(tt.args.rawRule, tt.args.filename, tt.args.lineNumber)
|
||||
got, err := NewRule(tt.args.rawRule, tt.args.filename, tt.args.lineNumber)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
@@ -247,3 +246,22 @@ func TestNew(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEvaluate(b *testing.B) {
|
||||
it := data.Item{
|
||||
ID: 0,
|
||||
Name: "Axe",
|
||||
Quality: item.QualitySuperior,
|
||||
}
|
||||
|
||||
rule, err := NewRule(
|
||||
"[type] == amulet && [quality] == crafted # ([shapeshiftingskilltab] >= 2 || [elementalskilltab] >= 2 || [druidsummoningskilltab] >= 2) && [fcr] >= 10 && ([strength]+[maxhp]+[maxmana] >= 60 || [dexterity]+[maxhp]+[maxmana] >= 60 || [strength]+[dexterity]+[maxhp] >= 50 || [strength]+[dexterity]+[maxmana] >= 55)",
|
||||
"test",
|
||||
1,
|
||||
)
|
||||
require.NoError(b, err)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
rule.Evaluate(it)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user