compiler: allow to declare global variables in multiple files

Traverse and count globals across all used files.

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2020-07-28 10:59:21 +03:00
parent babd84ec10
commit 7009417325
6 changed files with 36 additions and 8 deletions

View file

@ -28,20 +28,18 @@ func (c *codegen) newGlobal(name string) {
// 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(fs ...*ast.File) int { func (c *codegen) traverseGlobals() int {
var n int var n int
for _, f := range fs { c.ForEachFile(func(f *ast.File) {
n += countGlobals(f) n += countGlobals(f)
} })
if n != 0 { if n != 0 {
if n > 255 { if n > 255 {
c.prog.BinWriter.Err = errors.New("too many global variables") c.prog.BinWriter.Err = errors.New("too many global variables")
return 0 return 0
} }
emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)}) emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)})
for _, f := range fs { c.ForEachFile(c.convertGlobals)
c.convertGlobals(f)
}
} }
return n return n
} }

View file

@ -252,7 +252,7 @@ func (c *codegen) emitDefault(t types.Type) {
// convertGlobals traverses the AST and only converts global declarations. // convertGlobals traverses the AST and only converts global declarations.
// If we call this in convertFuncDecl then it will load all global variables // If we call this in convertFuncDecl then it will load all global variables
// into the scope of the function. // into the scope of the function.
func (c *codegen) convertGlobals(f ast.Node) { func (c *codegen) convertGlobals(f *ast.File) {
ast.Inspect(f, func(node ast.Node) bool { ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) { switch n := node.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
@ -1425,7 +1425,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
} }
c.mainPkg = pkg c.mainPkg = pkg
n := c.traverseGlobals(pkg.Files...) n := c.traverseGlobals()
if n > 0 { if n > 0 {
emit.Opcode(c.prog.BinWriter, opcode.RET) emit.Opcode(c.prog.BinWriter, opcode.RET)
c.initEndOffset = c.prog.Len() c.initEndOffset = c.prog.Len()

View file

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"go/ast"
"go/parser" "go/parser"
"io" "io"
"io/ioutil" "io/ioutil"
@ -42,6 +43,16 @@ type buildInfo struct {
program *loader.Program program *loader.Program
} }
// ForEachFile executes fn on each file used in current program.
func (c *codegen) ForEachFile(fn func(*ast.File)) {
for _, pkg := range c.buildInfo.program.AllPackages {
c.typeInfo = &pkg.Info
for _, f := range pkg.Files {
fn(f)
}
}
}
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)

View file

@ -127,3 +127,12 @@ func TestContractWithNoMain(t *testing.T) {
require.Equal(t, 1, v.Estack().Len()) require.Equal(t, 1, v.Estack().Len())
require.Equal(t, big.NewInt(42), v.PopResult()) require.Equal(t, big.NewInt(42), v.PopResult())
} }
func TestMultipleFiles(t *testing.T) {
src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
func Main() int {
return multi.Sum()
}`
eval(t, src, big.NewInt(42))
}

3
pkg/compiler/testdata/multi/file1.go vendored Normal file
View file

@ -0,0 +1,3 @@
package multi
var SomeVar12 = 12

7
pkg/compiler/testdata/multi/file2.go vendored Normal file
View file

@ -0,0 +1,7 @@
package multi
var SomeVar30 = 30
func Sum() int {
return SomeVar12 + SomeVar30
}