Skip to content

Commit caa7d52

Browse files
authored
Simplify ast nodes (#503)
* Move FiendIndex and MethodIndex out of ast nodes * Remove println statement * Add method compiler tests
1 parent d3b30e2 commit caa7d52

File tree

7 files changed

+155
-87
lines changed

7 files changed

+155
-87
lines changed

ast/node.go

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,7 @@ type NilNode struct {
5858
// IdentifierNode represents an identifier.
5959
type IdentifierNode struct {
6060
base
61-
Value string // Name of the identifier. Like "foo" in "foo.bar".
62-
FieldIndex []int // Internal. Index of the field in the list of fields.
63-
Method bool // Internal. If true then the identifier is a method call.
64-
MethodIndex int // Internal. Index of the method in the list of methods.
65-
}
66-
67-
// SetFieldIndex sets the field index of the identifier.
68-
func (n *IdentifierNode) SetFieldIndex(field []int) {
69-
n.FieldIndex = field
70-
}
71-
72-
// SetMethodIndex sets the method index of the identifier.
73-
func (n *IdentifierNode) SetMethodIndex(methodIndex int) {
74-
n.Method = true
75-
n.MethodIndex = methodIndex
61+
Value string // Name of the identifier. Like "foo" in "foo.bar".
7662
}
7763

7864
// IntegerNode represents an integer.
@@ -146,26 +132,9 @@ type ChainNode struct {
146132
// array[0]
147133
type MemberNode struct {
148134
base
149-
Node Node // Node of the member access. Like "foo" in "foo.bar".
150-
Property Node // Property of the member access. For property access it is a StringNode.
151-
Optional bool // If true then the member access is optional. Like "foo?.bar".
152-
Name string // Internal. Name of the filed or method. Used for error reporting.
153-
FieldIndex []int // Internal. Index sequence of fields. Generated by type checker.
154-
155-
// TODO: Combine Method and MethodIndex into a single MethodIndex field of &int type.
156-
Method bool // Internal. If true then the member access is a method call.
157-
MethodIndex int // Internal. Index of the method in the list of methods. Generated by type checker.
158-
}
159-
160-
// SetFieldIndex sets the field index of the member access.
161-
func (n *MemberNode) SetFieldIndex(field []int) {
162-
n.FieldIndex = field
163-
}
164-
165-
// SetMethodIndex sets the method index of the member access.
166-
func (n *MemberNode) SetMethodIndex(methodIndex int) {
167-
n.Method = true
168-
n.MethodIndex = methodIndex
135+
Node Node // Node of the member access. Like "foo" in "foo.bar".
136+
Property Node // Property of the member access. For property access it is a StringNode.
137+
Optional bool // If true then the member access is optional. Like "foo?.bar".
169138
}
170139

171140
// SliceNode represents access to a slice of an array.

checker/checker.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -166,23 +166,13 @@ func (v *checker) IdentifierNode(node *ast.IdentifierNode) (reflect.Type, info)
166166
return v.env(node, node.Value, true)
167167
}
168168

169-
type NodeWithIndexes interface {
170-
ast.Node
171-
SetFieldIndex(field []int)
172-
SetMethodIndex(methodIndex int)
173-
}
174-
175169
// env method returns type of environment variable. env only lookups for
176170
// environment variables, no builtins, no custom functions.
177-
func (v *checker) env(node NodeWithIndexes, name string, strict bool) (reflect.Type, info) {
171+
func (v *checker) env(node ast.Node, name string, strict bool) (reflect.Type, info) {
178172
if t, ok := v.config.Types[name]; ok {
179173
if t.Ambiguous {
180174
return v.error(node, "ambiguous identifier %v", name)
181175
}
182-
node.SetFieldIndex(t.FieldIndex)
183-
if t.Method {
184-
node.SetMethodIndex(t.MethodIndex)
185-
}
186176
return t.Type, info{method: t.Method}
187177
}
188178
if v.config.Strict && strict {
@@ -477,8 +467,6 @@ func (v *checker) MemberNode(node *ast.MemberNode) (reflect.Type, info) {
477467
// the same interface.
478468
return m.Type, info{}
479469
} else {
480-
node.SetMethodIndex(m.Index)
481-
node.Name = name.Value
482470
return m.Type, info{method: true}
483471
}
484472
}
@@ -508,8 +496,6 @@ func (v *checker) MemberNode(node *ast.MemberNode) (reflect.Type, info) {
508496
if name, ok := node.Property.(*ast.StringNode); ok {
509497
propertyName := name.Value
510498
if field, ok := fetchField(base, propertyName); ok {
511-
node.FieldIndex = field.Index
512-
node.Name = propertyName
513499
return field.Type, info{}
514500
}
515501
if len(v.parents) > 1 {

checker/info.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package checker
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/expr-lang/expr/ast"
7+
"github.com/expr-lang/expr/conf"
8+
)
9+
10+
func FieldIndex(types conf.TypesTable, node ast.Node) (bool, []int, string) {
11+
switch n := node.(type) {
12+
case *ast.IdentifierNode:
13+
if t, ok := types[n.Value]; ok && len(t.FieldIndex) > 0 {
14+
return true, t.FieldIndex, n.Value
15+
}
16+
case *ast.MemberNode:
17+
base := n.Node.Type()
18+
if kind(base) == reflect.Ptr {
19+
base = base.Elem()
20+
}
21+
if kind(base) == reflect.Struct {
22+
if prop, ok := n.Property.(*ast.StringNode); ok {
23+
name := prop.Value
24+
if field, ok := fetchField(base, name); ok {
25+
return true, field.Index, name
26+
}
27+
}
28+
}
29+
}
30+
return false, nil, ""
31+
}
32+
33+
func MethodIndex(types conf.TypesTable, node ast.Node) (bool, int, string) {
34+
switch n := node.(type) {
35+
case *ast.IdentifierNode:
36+
if t, ok := types[n.Value]; ok {
37+
return t.Method, t.MethodIndex, n.Value
38+
}
39+
case *ast.MemberNode:
40+
if name, ok := n.Property.(*ast.StringNode); ok {
41+
base := n.Node.Type()
42+
if base != nil && base.Kind() != reflect.Interface {
43+
if m, ok := base.MethodByName(name.Value); ok {
44+
return true, m.Index, name.Value
45+
}
46+
}
47+
}
48+
}
49+
return false, 0, ""
50+
}

compiler/compiler.go

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/expr-lang/expr/ast"
88
"github.com/expr-lang/expr/builtin"
9+
"github.com/expr-lang/expr/checker"
910
"github.com/expr-lang/expr/conf"
1011
"github.com/expr-lang/expr/file"
1112
"github.com/expr-lang/expr/parser"
@@ -34,6 +35,7 @@ func Compile(tree *parser.Tree, config *conf.Config) (program *Program, err erro
3435
if config != nil {
3536
c.mapEnv = config.MapEnv
3637
c.cast = config.Expect
38+
c.types = config.Types
3739
}
3840

3941
c.compile(tree.Node)
@@ -75,6 +77,7 @@ type compiler struct {
7577
nodes []ast.Node
7678
chains [][]int
7779
arguments []int
80+
types conf.TypesTable
7881
}
7982

8083
type scope struct {
@@ -254,15 +257,15 @@ func (c *compiler) IdentifierNode(node *ast.IdentifierNode) {
254257
}
255258
if c.mapEnv {
256259
c.emit(OpLoadFast, c.addConstant(node.Value))
257-
} else if len(node.FieldIndex) > 0 {
260+
} else if ok, index, name := checker.FieldIndex(c.types, node); ok {
258261
c.emit(OpLoadField, c.addConstant(&runtime.Field{
259-
Index: node.FieldIndex,
260-
Path: []string{node.Value},
262+
Index: index,
263+
Path: []string{name},
261264
}))
262-
} else if node.Method {
265+
} else if ok, index, name := checker.MethodIndex(c.types, node); ok {
263266
c.emit(OpLoadMethod, c.addConstant(&runtime.Method{
264-
Name: node.Value,
265-
Index: node.MethodIndex,
267+
Name: name,
268+
Index: index,
266269
}))
267270
} else {
268271
c.emit(OpLoadConst, c.addConstant(node.Value))
@@ -559,36 +562,43 @@ func (c *compiler) ChainNode(node *ast.ChainNode) {
559562
}
560563

561564
func (c *compiler) MemberNode(node *ast.MemberNode) {
562-
if node.Method {
565+
if ok, index, name := checker.MethodIndex(c.types, node); ok {
563566
c.compile(node.Node)
564567
c.emit(OpMethod, c.addConstant(&runtime.Method{
565-
Name: node.Name,
566-
Index: node.MethodIndex,
568+
Name: name,
569+
Index: index,
567570
}))
568571
return
569572
}
570573
op := OpFetch
571-
index := node.FieldIndex
572-
path := []string{node.Name}
573574
base := node.Node
574-
if len(node.FieldIndex) > 0 {
575+
576+
ok, index, nodeName := checker.FieldIndex(c.types, node)
577+
path := []string{nodeName}
578+
579+
if ok {
575580
op = OpFetchField
576581
for !node.Optional {
577-
ident, ok := base.(*ast.IdentifierNode)
578-
if ok && len(ident.FieldIndex) > 0 {
579-
index = append(ident.FieldIndex, index...)
580-
path = append([]string{ident.Value}, path...)
581-
c.emitLocation(ident.Location(), OpLoadField, c.addConstant(
582-
&runtime.Field{Index: index, Path: path},
583-
))
584-
return
582+
if ident, isIdent := base.(*ast.IdentifierNode); isIdent {
583+
if ok, identIndex, name := checker.FieldIndex(c.types, ident); ok {
584+
index = append(identIndex, index...)
585+
path = append([]string{name}, path...)
586+
c.emitLocation(ident.Location(), OpLoadField, c.addConstant(
587+
&runtime.Field{Index: index, Path: path},
588+
))
589+
return
590+
}
585591
}
586-
member, ok := base.(*ast.MemberNode)
587-
if ok && len(member.FieldIndex) > 0 {
588-
index = append(member.FieldIndex, index...)
589-
path = append([]string{member.Name}, path...)
590-
node = member
591-
base = member.Node
592+
593+
if member, isMember := base.(*ast.MemberNode); isMember {
594+
if ok, memberIndex, name := checker.FieldIndex(c.types, member); ok {
595+
index = append(memberIndex, index...)
596+
path = append([]string{name}, path...)
597+
node = member
598+
base = member.Node
599+
} else {
600+
break
601+
}
592602
} else {
593603
break
594604
}

compiler/compiler_test.go

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ type B struct {
2424
}
2525
}
2626

27+
func (B) FuncInB() int {
28+
return 0
29+
}
30+
2731
type Env struct {
2832
A struct {
2933
_ byte
@@ -33,12 +37,20 @@ type Env struct {
3337
}
3438
}
3539

40+
// AFunc is a method what goes before Func in the alphabet.
41+
func (e Env) AFunc() int {
42+
return 0
43+
}
44+
45+
func (e Env) Func() B {
46+
return B{}
47+
}
48+
3649
func TestCompile(t *testing.T) {
37-
type test struct {
38-
input string
39-
program vm.Program
40-
}
41-
var tests = []test{
50+
var tests = []struct {
51+
code string
52+
want vm.Program
53+
}{
4254
{
4355
`65535`,
4456
vm.Program{
@@ -271,13 +283,53 @@ func TestCompile(t *testing.T) {
271283
Arguments: []int{0, 0, 1, 0},
272284
},
273285
},
286+
{
287+
`Func()`,
288+
vm.Program{
289+
Constants: []any{
290+
&runtime.Method{
291+
Index: 1,
292+
Name: "Func",
293+
},
294+
},
295+
Bytecode: []vm.Opcode{
296+
vm.OpLoadMethod,
297+
vm.OpCall,
298+
},
299+
Arguments: []int{0, 0},
300+
},
301+
},
302+
{
303+
`Func().FuncInB()`,
304+
vm.Program{
305+
Constants: []any{
306+
&runtime.Method{
307+
Index: 1,
308+
Name: "Func",
309+
},
310+
&runtime.Method{
311+
Index: 0,
312+
Name: "FuncInB",
313+
},
314+
},
315+
Bytecode: []vm.Opcode{
316+
vm.OpLoadMethod,
317+
vm.OpCall,
318+
vm.OpMethod,
319+
vm.OpCallTyped,
320+
},
321+
Arguments: []int{0, 0, 1, 10},
322+
},
323+
},
274324
}
275325

276326
for _, test := range tests {
277-
program, err := expr.Compile(test.input, expr.Env(Env{}), expr.Optimize(false))
278-
require.NoError(t, err, test.input)
327+
t.Run(test.code, func(t *testing.T) {
328+
program, err := expr.Compile(test.code, expr.Env(Env{}), expr.Optimize(false))
329+
require.NoError(t, err)
279330

280-
assert.Equal(t, test.program.Disassemble(), program.Disassemble(), test.input)
331+
assert.Equal(t, test.want.Disassemble(), program.Disassemble())
332+
})
281333
}
282334
}
283335

test/interface_method/interface_method_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package interface_method_test
33
import (
44
"testing"
55

6-
"github.com/expr-lang/expr"
76
"github.com/stretchr/testify/assert"
87
"github.com/stretchr/testify/require"
8+
9+
"github.com/expr-lang/expr"
910
)
1011

1112
type Bar interface {
@@ -39,7 +40,6 @@ func TestInterfaceMethod(t *testing.T) {
3940
"var": FooImpl{},
4041
}
4142
p, err := expr.Compile(`var.Foo().Bar()`, expr.Env(env))
42-
4343
assert.NoError(t, err)
4444

4545
out, err := expr.Run(p, env)

test/operator/operator_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import (
44
"testing"
55
"time"
66

7+
"github.com/stretchr/testify/require"
8+
79
"github.com/expr-lang/expr"
810
"github.com/expr-lang/expr/test/mock"
9-
"github.com/stretchr/testify/require"
1011
)
1112

1213
func TestOperator_struct(t *testing.T) {

0 commit comments

Comments
 (0)