Skip to content
This repository was archived by the owner on Dec 23, 2025. It is now read-only.

Commit bc0fea6

Browse files
committed
feat: support index slice, map and array
Change-Id: I4c4717f00d743d1330b147d6525f3cb41ed202e9
1 parent 237f520 commit bc0fea6

File tree

5 files changed

+106
-31
lines changed

5 files changed

+106
-31
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ In development
1212
type T struct {
1313
A int `tagexpr:"$<0||$>=100"`
1414
B string `tagexpr:"len($)>1 || regexp('^\\w*$')"`
15-
C bool `tagexpr:"{expr1:(G)$['J']>0 && $}{expr2:'C must be true when T.G.J>0'}"`
15+
C bool `tagexpr:"{expr1:(F.G)$>0 && $}{expr2:'C must be true when T.F.G>0'}"`
1616
D []string `tagexpr:"{expr1:len($)>0 && $[0]=='D'} {expr2:sprintf('Invalid D:%s',$)}"`
17-
E map[string]int `tagexpr:"{expr1:$k!='' && $v>0}{expr2:$['E']>0}"`
18-
F map[string][]int `tagexpr:"$$v>0 && len($['F'])>0 && $['F'][0]>1"`
19-
G struct{ J int }
17+
E map[string]int `tagexpr:"len($)"`
18+
F struct{ G int }
2019
}
2120
```
2221

@@ -56,17 +55,19 @@ NOTE: **The `exprName` under the same struct field cannot be the same!**
5655
|`\|\|`|Logic `or`|
5756
|`()`|Expression group|
5857
|`(X)$`|Struct field value named X|
58+
|`(X.Y)$`|Struct field value named X.Y|
5959
|`$`|Shorthand for `(X)$`, omit `(X)` to indicate current struct field value|
60-
|`(X)$['A']`|Map or struct field value with name A in the struct field X|
60+
|`(X)$['A']`|Map value with key A in the struct field X|
6161
|`(X)$[0]`|The 0th element of the struct field X(type: map, slice, array)|
62-
|`(X)$k`|Traverse each element key of the struct field X(type: map, slice, array)|
63-
|`(X)$v`|Traverse each element value of the struct field X(type: map, slice, array)|
6462
|`len((X)$)`|Built-in function `len`, the length of struct field X|
6563
|`len()`|Built-in function `len`, the length of the current struct field|
6664
|`regexp('^\\w*$', (X)$)`|Regular match the struct field X, return boolean|
6765
|`regexp('^\\w*$')`|Regular match the current struct field, return boolean|
6866
|`sprintf('X value: %v', (X)$)`|`fmt.Sprintf`, format the value of struct field X|
6967

68+
<!-- |`(X)$k`|Traverse each element key of the struct field X(type: map, slice, array)|
69+
|`(X)$v`|Traverse each element value of the struct field X(type: map, slice, array)| -->
70+
7071
<!-- |`&`|Integer bitwise `and`|
7172
|`\|`|Integer bitwise `or`|
7273
|`^`|Integer bitwise `not` or `xor`|

expr_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ func TestBuiltInFunc(t *testing.T) {
152152
{expr: "len('abc')", val: 3.0},
153153
{expr: "len('abc')+2*2/len('cd')", val: 5.0},
154154
{expr: "len(0)", val: nil},
155-
{expr: "len()", val: nil},
156155

157156
{expr: "regexp('a\\d','a0')", val: true},
158157
{expr: "regexp('^a\\d$','a0')", val: true},

spec_selector.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,31 @@ func (p *Expr) readSelectorExprNode(expr *string) ExprNode {
3333
return operand
3434
}
3535

36-
var selectorRegexp = regexp.MustCompile(`^(\([ \t]*[a-zA-Z_]{1}\w*[ \t]*\))?(\$[kv]?)(\[[ \t]*\S+[ \t]*\])*([\+\-\*\/%><\|&!=\^ \t\\]|$)`)
36+
var selectorRegexp = regexp.MustCompile(`^(\([ \t]*[A-Za-z_]+[A-Za-z0-9_\.]*[ \t]*\))?(\$)([\[\+\-\*\/%><\|&!=\^ \t\\]|$)`)
3737

3838
func findSelector(expr *string) (field string, name string, subSelector []string, found bool) {
39-
a := selectorRegexp.FindAllStringSubmatch(*expr, -1)
39+
raw := *expr
40+
a := selectorRegexp.FindAllStringSubmatch(raw, -1)
4041
if len(a) != 1 {
4142
return
4243
}
43-
length := len(a[0][0])
4444
r := a[0]
4545
if s0 := r[1]; len(s0) > 0 {
4646
field = strings.TrimSpace(s0[1 : len(s0)-1])
4747
}
4848
name = r[2]
49-
s := r[3]
49+
*expr = (*expr)[len(a[0][0])-len(r[3]):]
5050
for {
51-
sub := readPairedSymbol(&s, '[', ']')
51+
sub := readPairedSymbol(expr, '[', ']')
5252
if sub == nil {
5353
break
5454
}
5555
if *sub == "" || (*sub)[0] == '[' {
56+
*expr = raw
5657
return "", "", nil, false
5758
}
5859
subSelector = append(subSelector, strings.TrimSpace(*sub))
5960
}
60-
if len(r[4]) == 1 {
61-
length--
62-
}
63-
*expr = (*expr)[length:]
6461
found = true
6562
return
6663
}

tagexpr.go

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ type TagExpr struct {
306306
// Eval evaluate the value of the struct tag expression by the selector expression.
307307
// format: fieldName, fieldName.exprName, fieldName1.fieldName2.exprName1
308308
func (t *TagExpr) Eval(selector string) interface{} {
309+
defer func() {
310+
if recover() != nil {
311+
// fmt.Println(goutil.BytesToString(goutil.PanicTrace(1)))
312+
}
313+
}()
309314
expr, ok := t.s.exprs[selector]
310315
if !ok {
311316
return nil
@@ -315,6 +320,11 @@ func (t *TagExpr) Eval(selector string) interface{} {
315320

316321
// Range loop through each tag expression
317322
func (t *TagExpr) Range(fn func(selector string, eval func() interface{})) {
323+
defer func() {
324+
if recover() != nil {
325+
// fmt.Println(goutil.BytesToString(goutil.PanicTrace(1)))
326+
}
327+
}()
318328
for selector, expr := range t.s.exprs {
319329
fn(selector, func() interface{} {
320330
return expr.run(getFieldSelector(selector), t)
@@ -327,13 +337,62 @@ func (t *TagExpr) getValue(field string, subFields []interface{}) (v interface{}
327337
if !ok {
328338
return nil
329339
}
330-
_ = subFields // TODO
331340
if f.valueGetter == nil {
332341
return nil
333342
}
334-
return f.valueGetter(t.ptr)
343+
v = f.valueGetter(t.ptr)
344+
if len(subFields) == 0 {
345+
return v
346+
}
347+
vv := reflect.ValueOf(v)
348+
fmt.Println("=======", subFields, vv.Kind(), vv.Interface())
349+
// if len(subFields) == 0 {
350+
// return v
351+
// }
352+
for _, k := range subFields {
353+
for vv.Kind() == reflect.Ptr {
354+
vv = vv.Elem()
355+
}
356+
switch vv.Kind() {
357+
case reflect.Slice, reflect.Array, reflect.String:
358+
if float, ok := k.(float64); ok {
359+
idx := int(float)
360+
if idx >= vv.Len() {
361+
return nil
362+
}
363+
vv = vv.Index(idx)
364+
} else {
365+
return nil
366+
}
367+
case reflect.Map:
368+
vv = vv.MapIndex(reflect.ValueOf(k).Convert(vv.Type().Key()))
369+
default:
370+
return nil
371+
}
372+
}
373+
for vv.Kind() == reflect.Ptr {
374+
vv = vv.Elem()
375+
}
376+
switch vv.Kind() {
377+
default:
378+
if vv.CanInterface() {
379+
return vv.Interface()
380+
}
381+
return nil
382+
case reflect.String:
383+
return vv.String()
384+
case reflect.Bool:
385+
return vv.Bool()
386+
case reflect.Float32, reflect.Float64:
387+
return vv.Float()
388+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
389+
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
390+
return vv.Convert(float64Type).Float()
391+
}
335392
}
336393

394+
var float64Type = reflect.TypeOf(float64(0))
395+
337396
func getFieldSelector(selector string) string {
338397
idx := strings.LastIndex(selector, ".")
339398
if idx == -1 {

tagexpr_test.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ import (
88
func TestVMFunc(t *testing.T) {
99
g := &struct {
1010
_ int
11-
h bool `tagexpr:"$"`
12-
}{h: true}
11+
h string `tagexpr:"$"`
12+
s []string
13+
m map[string][]string
14+
}{
15+
h: "haha",
16+
s: []string{"1"},
17+
m: map[string][]string{"0": {"2"}},
18+
}
1319
d := "ddd"
1420
e := new(int)
1521
*e = 3
@@ -21,13 +27,15 @@ func TestVMFunc(t *testing.T) {
2127
{
2228
tagName: "tagexpr",
2329
structure: &struct {
24-
A int `tagexpr:"$>0&&$<10"`
25-
b string `tagexpr:"{is:$=='test'}{msg:sprintf('want: test, but got: %s',$)}"`
26-
c float32 `tagexpr:"(A)$+$"`
27-
d *string `tagexpr:"$"`
28-
e **int `tagexpr:"$"`
29-
f *[3]int `tagexpr:"{x:len($)}{y:len()}"`
30-
g string `tagexpr:"{x:regexp('g\\d{3}$',$)}{y:regexp('g\\d{3}$')}"`
30+
A int `tagexpr:"$>0&&$<10"`
31+
b string `tagexpr:"{is:$=='test'}{msg:sprintf('want: test, but got: %s',$)}"`
32+
c float32 `tagexpr:"(A)$+$"`
33+
d *string `tagexpr:"$"`
34+
e **int `tagexpr:"$"`
35+
f *[3]int `tagexpr:"{x:len($)}{y:len()}"`
36+
g string `tagexpr:"{x:regexp('g\\d{3}$',$)}{y:regexp('g\\d{3}$')}"`
37+
h []string `tagexpr:"{x:$[1]}{y:$[10]}"`
38+
i map[string]int `tagexpr:"{x:$['a']}{y:$[0]}"`
3139
}{
3240
A: 5.0,
3341
b: "x",
@@ -36,6 +44,8 @@ func TestVMFunc(t *testing.T) {
3644
e: &e,
3745
f: new([3]int),
3846
g: "g123",
47+
h: []string{"", "hehe"},
48+
i: map[string]int{"a": 7},
3949
},
4050
tests: map[string]interface{}{
4151
"A.$": true,
@@ -48,6 +58,10 @@ func TestVMFunc(t *testing.T) {
4858
"f.y": float64(3),
4959
"g.x": true,
5060
"g.y": true,
61+
"h.x": "hehe",
62+
"h.y": nil,
63+
"i.x": 7.0,
64+
"i.y": nil,
5165
},
5266
},
5367
{
@@ -65,8 +79,11 @@ func TestVMFunc(t *testing.T) {
6579
}
6680
g **struct {
6781
_ int
68-
h bool `tagexpr:"$"`
69-
}
82+
h string `tagexpr:"$"`
83+
s []string
84+
m map[string][]string
85+
} `tagexpr:"$['h']"`
86+
i string `tagexpr:"(g.s)$[0]+(g.m)$['0'][0]==$"`
7087
}{
7188
A: 5.0,
7289
b: "x",
@@ -79,14 +96,16 @@ func TestVMFunc(t *testing.T) {
7996
f bool `tagexpr:"$"`
8097
}{f: true},
8198
g: &g,
99+
i: "12",
82100
},
83101
tests: map[string]interface{}{
84102
"A.$": true,
85103
"b.is": false,
86104
"b.msg": "want: test, but got: x",
87105
"c.d.$": true,
88106
"e.f.$": true,
89-
"g.h.$": true,
107+
"g.h.$": "haha",
108+
"i.$": true,
90109
},
91110
},
92111
}

0 commit comments

Comments
 (0)