compiler: allow to use exported variables
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
b8d7e93459
commit
6df019913d
5 changed files with 124 additions and 23 deletions
|
@ -21,10 +21,19 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// newGlobal creates new global variable.
|
// newGlobal creates new global variable.
|
||||||
func (c *codegen) newGlobal(name string) {
|
func (c *codegen) newGlobal(pkg string, name string) {
|
||||||
|
name = c.getIdentName(pkg, name)
|
||||||
c.globals[name] = len(c.globals)
|
c.globals[name] = len(c.globals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getIdentName returns fully-qualified name for a variable.
|
||||||
|
func (c *codegen) getIdentName(pkg string, name string) string {
|
||||||
|
if fullName, ok := c.importMap[pkg]; ok {
|
||||||
|
pkg = fullName
|
||||||
|
}
|
||||||
|
return pkg + "." + name
|
||||||
|
}
|
||||||
|
|
||||||
// traverseGlobals visits and initializes global variables.
|
// traverseGlobals visits and initializes global variables.
|
||||||
// and returns number of variables initialized.
|
// and returns number of variables initialized.
|
||||||
func (c *codegen) traverseGlobals() int {
|
func (c *codegen) traverseGlobals() int {
|
||||||
|
|
|
@ -62,6 +62,9 @@ type codegen struct {
|
||||||
// initEndOffset specifies the end of the initialization method.
|
// initEndOffset specifies the end of the initialization method.
|
||||||
initEndOffset int
|
initEndOffset int
|
||||||
|
|
||||||
|
// importMap contains mapping from package aliases to full package names for the current file.
|
||||||
|
importMap map[string]string
|
||||||
|
|
||||||
// mainPkg is a main package metadata.
|
// mainPkg is a main package metadata.
|
||||||
mainPkg *loader.PackageInfo
|
mainPkg *loader.PackageInfo
|
||||||
|
|
||||||
|
@ -167,14 +170,16 @@ func (c *codegen) emitStoreStructField(i int) {
|
||||||
|
|
||||||
// getVarIndex returns variable type and position in corresponding slot,
|
// getVarIndex returns variable type and position in corresponding slot,
|
||||||
// according to current scope.
|
// according to current scope.
|
||||||
func (c *codegen) getVarIndex(name string) (varType, int) {
|
func (c *codegen) getVarIndex(pkg string, name string) (varType, int) {
|
||||||
if c.scope != nil {
|
if pkg == "" {
|
||||||
vt, val := c.scope.vars.getVarIndex(name)
|
if c.scope != nil {
|
||||||
if val >= 0 {
|
vt, val := c.scope.vars.getVarIndex(name)
|
||||||
return vt, val
|
if val >= 0 {
|
||||||
|
return vt, val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i, ok := c.globals[name]; ok {
|
if i, ok := c.globals[c.getIdentName(pkg, name)]; ok {
|
||||||
return varGlobal, i
|
return varGlobal, i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,8 +200,8 @@ func getBaseOpcode(t varType) (opcode.Opcode, opcode.Opcode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitLoadVar loads specified variable to the evaluation stack.
|
// emitLoadVar loads specified variable to the evaluation stack.
|
||||||
func (c *codegen) emitLoadVar(name string) {
|
func (c *codegen) emitLoadVar(pkg string, name string) {
|
||||||
t, i := c.getVarIndex(name)
|
t, i := c.getVarIndex(pkg, name)
|
||||||
base, _ := getBaseOpcode(t)
|
base, _ := getBaseOpcode(t)
|
||||||
if i < 7 {
|
if i < 7 {
|
||||||
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
||||||
|
@ -206,12 +211,12 @@ func (c *codegen) emitLoadVar(name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitStoreVar stores top value from the evaluation stack in the specified variable.
|
// emitStoreVar stores top value from the evaluation stack in the specified variable.
|
||||||
func (c *codegen) emitStoreVar(name string) {
|
func (c *codegen) emitStoreVar(pkg string, name string) {
|
||||||
if name == "_" {
|
if name == "_" {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
emit.Opcode(c.prog.BinWriter, opcode.DROP)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t, i := c.getVarIndex(name)
|
t, i := c.getVarIndex(pkg, name)
|
||||||
_, base := getBaseOpcode(t)
|
_, base := getBaseOpcode(t)
|
||||||
if i < 7 {
|
if i < 7 {
|
||||||
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
emit.Opcode(c.prog.BinWriter, base+opcode.Opcode(i))
|
||||||
|
@ -368,7 +373,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
for _, id := range t.Names {
|
for _, id := range t.Names {
|
||||||
if c.scope == nil {
|
if c.scope == nil {
|
||||||
// it is a global declaration
|
// it is a global declaration
|
||||||
c.newGlobal(id.Name)
|
c.newGlobal("", id.Name)
|
||||||
} else {
|
} else {
|
||||||
c.scope.newLocal(id.Name)
|
c.scope.newLocal(id.Name)
|
||||||
}
|
}
|
||||||
|
@ -380,7 +385,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
} else {
|
} else {
|
||||||
c.emitDefault(c.typeOf(t.Type))
|
c.emitDefault(c.typeOf(t.Type))
|
||||||
}
|
}
|
||||||
c.emitStoreVar(t.Names[i].Name)
|
c.emitStoreVar("", t.Names[i].Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,13 +416,19 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
if !isAssignOp && (i == 0 || !multiRet) {
|
if !isAssignOp && (i == 0 || !multiRet) {
|
||||||
ast.Walk(c, n.Rhs[i])
|
ast.Walk(c, n.Rhs[i])
|
||||||
}
|
}
|
||||||
c.emitStoreVar(t.Name)
|
c.emitStoreVar("", t.Name)
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
if !isAssignOp {
|
if !isAssignOp {
|
||||||
ast.Walk(c, n.Rhs[i])
|
ast.Walk(c, n.Rhs[i])
|
||||||
}
|
}
|
||||||
strct, ok := c.typeOf(t.X).Underlying().(*types.Struct)
|
typ := c.typeOf(t.X)
|
||||||
|
if typ == nil {
|
||||||
|
// Store to other package global variable.
|
||||||
|
c.emitStoreVar(t.X.(*ast.Ident).Name, t.Sel.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
strct, ok := typ.Underlying().(*types.Struct)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.prog.Err = fmt.Errorf("nested selector assigns not supported yet")
|
c.prog.Err = fmt.Errorf("nested selector assigns not supported yet")
|
||||||
return nil
|
return nil
|
||||||
|
@ -442,7 +453,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
case *ast.SliceExpr:
|
case *ast.SliceExpr:
|
||||||
name := n.X.(*ast.Ident).Name
|
name := n.X.(*ast.Ident).Name
|
||||||
c.emitLoadVar(name)
|
c.emitLoadVar("", name)
|
||||||
|
|
||||||
if n.Low != nil {
|
if n.Low != nil {
|
||||||
ast.Walk(c, n.Low)
|
ast.Walk(c, n.Low)
|
||||||
|
@ -480,7 +491,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
for i := len(results.List) - 1; i >= 0; i-- {
|
for i := len(results.List) - 1; i >= 0; i-- {
|
||||||
names := results.List[i].Names
|
names := results.List[i].Names
|
||||||
for j := len(names) - 1; j >= 0; j-- {
|
for j := len(names) - 1; j >= 0; j-- {
|
||||||
c.emitLoadVar(names[j].Name)
|
c.emitLoadVar("", names[j].Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -595,7 +606,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
} else if n.Name == "nil" {
|
} else if n.Name == "nil" {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
|
emit.Opcode(c.prog.BinWriter, opcode.PUSHNULL)
|
||||||
} else {
|
} else {
|
||||||
c.emitLoadVar(n.Name)
|
c.emitLoadVar("", n.Name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -789,7 +800,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
if isString(c.typeOf(n.Fun)) {
|
if isString(c.typeOf(n.Fun)) {
|
||||||
c.emitConvert(stackitem.ByteArrayT)
|
c.emitConvert(stackitem.ByteArrayT)
|
||||||
} else if isFunc {
|
} else if isFunc {
|
||||||
c.emitLoadVar(name)
|
c.emitLoadVar("", name)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
|
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
|
||||||
}
|
}
|
||||||
case isSyscall(f):
|
case isSyscall(f):
|
||||||
|
@ -801,7 +812,15 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
strct, ok := c.typeOf(n.X).Underlying().(*types.Struct)
|
typ := c.typeOf(n.X)
|
||||||
|
if typ == nil {
|
||||||
|
// This is a global variable from a package.
|
||||||
|
pkgAlias := n.X.(*ast.Ident).Name
|
||||||
|
pkgPath := c.importMap[pkgAlias]
|
||||||
|
c.emitLoadVar(pkgPath, n.Sel.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
strct, ok := typ.Underlying().(*types.Struct)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.prog.Err = fmt.Errorf("selectors are supported only on structs")
|
c.prog.Err = fmt.Errorf("selectors are supported only on structs")
|
||||||
return nil
|
return nil
|
||||||
|
@ -840,7 +859,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// for i := 0; i < 10; i++ {}
|
// for i := 0; i < 10; i++ {}
|
||||||
// Where the post stmt is ( i++ )
|
// Where the post stmt is ( i++ )
|
||||||
if ident, ok := n.X.(*ast.Ident); ok {
|
if ident, ok := n.X.(*ast.Ident); ok {
|
||||||
c.emitStoreVar(ident.Name)
|
c.emitStoreVar("", ident.Name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -992,7 +1011,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
} else {
|
} else {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
}
|
}
|
||||||
c.emitStoreVar(n.Key.(*ast.Ident).Name)
|
c.emitStoreVar("", n.Key.(*ast.Ident).Name)
|
||||||
}
|
}
|
||||||
if needValue {
|
if needValue {
|
||||||
if !isMap || !keyLoaded {
|
if !isMap || !keyLoaded {
|
||||||
|
@ -1005,7 +1024,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.SWAP) // key should be on top
|
emit.Opcode(c.prog.BinWriter, opcode.SWAP) // key should be on top
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
emit.Opcode(c.prog.BinWriter, opcode.PICKITEM)
|
||||||
}
|
}
|
||||||
c.emitStoreVar(n.Value.(*ast.Ident).Name)
|
c.emitStoreVar("", n.Value.(*ast.Ident).Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
ast.Walk(c, n.Body)
|
ast.Walk(c, n.Body)
|
||||||
|
|
|
@ -49,11 +49,29 @@ func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) {
|
||||||
for _, pkg := range c.buildInfo.program.AllPackages {
|
for _, pkg := range c.buildInfo.program.AllPackages {
|
||||||
c.typeInfo = &pkg.Info
|
c.typeInfo = &pkg.Info
|
||||||
for _, f := range pkg.Files {
|
for _, f := range pkg.Files {
|
||||||
|
c.fillImportMap(f, pkg.Pkg)
|
||||||
fn(f, pkg.Pkg)
|
fn(f, pkg.Pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fillImportMap fills import map for f.
|
||||||
|
func (c *codegen) fillImportMap(f *ast.File, pkg *types.Package) {
|
||||||
|
c.importMap = map[string]string{"": pkg.Path()}
|
||||||
|
for _, imp := range f.Imports {
|
||||||
|
// We need to load find package metadata because
|
||||||
|
// name specified in `package ...` decl, can be in
|
||||||
|
// conflict with package path.
|
||||||
|
pkgPath := strings.Trim(imp.Path.Value, `"`)
|
||||||
|
realPkg := c.buildInfo.program.Package(pkgPath)
|
||||||
|
name := realPkg.Pkg.Name()
|
||||||
|
if imp.Name != nil {
|
||||||
|
name = imp.Name.Name
|
||||||
|
}
|
||||||
|
c.importMap[name] = realPkg.Pkg.Path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getBuildInfo(src interface{}) (*buildInfo, error) {
|
func getBuildInfo(src interface{}) (*buildInfo, error) {
|
||||||
conf := loader.Config{ParserMode: parser.ParseComments}
|
conf := loader.Config{ParserMode: parser.ParseComments}
|
||||||
f, err := conf.ParseFile("", src)
|
f, err := conf.ParseFile("", src)
|
||||||
|
|
|
@ -136,3 +136,54 @@ func TestMultipleFiles(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
eval(t, src, big.NewInt(42))
|
eval(t, src, big.NewInt(42))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExportedVariable(t *testing.T) {
|
||||||
|
t.Run("Use", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
return multi.SomeVar12
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(12))
|
||||||
|
})
|
||||||
|
t.Run("ChangeAndUse", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
multi.SomeVar12 = 10
|
||||||
|
return multi.Sum()
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(40))
|
||||||
|
})
|
||||||
|
t.Run("PackageAlias", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import kek "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
kek.SomeVar12 = 10
|
||||||
|
return kek.Sum()
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(40))
|
||||||
|
})
|
||||||
|
t.Run("DifferentName", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/strange"
|
||||||
|
func Main() int {
|
||||||
|
normal.NormalVar = 42
|
||||||
|
return normal.NormalVar
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
})
|
||||||
|
t.Run("MultipleEqualNames", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
var SomeVar12 = 1
|
||||||
|
func Main() int {
|
||||||
|
SomeVar30 := 3
|
||||||
|
sum := SomeVar12 + multi.SomeVar30
|
||||||
|
sum += SomeVar30
|
||||||
|
sum += multi.SomeVar12
|
||||||
|
return sum
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(46))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
4
pkg/compiler/testdata/strange/normal.go
vendored
Normal file
4
pkg/compiler/testdata/strange/normal.go
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package normal
|
||||||
|
|
||||||
|
// NormalVar is pretty normal, nothing special.
|
||||||
|
var NormalVar = 7
|
Loading…
Reference in a new issue