Compiler update (basic sc ready) (#31)

* refactored structs, the scope is not needed anymore + fix passing struct in func arguments.

* implemented byte arrays and added runtime tests

* Added sc examples in compiler README + added quick nested if test.

* Updated README
This commit is contained in:
Anthony De Meulemeester 2018-02-27 10:04:24 +01:00 committed by GitHub
parent de3395fb51
commit 2345858238
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 464 additions and 215 deletions

3
.gitignore vendored
View file

@ -22,3 +22,6 @@ bin/
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
# anthdm todolists
/pkg/vm/compiler/todo.md

View file

@ -40,23 +40,21 @@ A complete toolkit for the NEO blockchain, including:
## Current State ## Current State
This project is still under heavy development. Still working on internal API's and project layout. T
his should not take longer than 2 weeks.
The project will exist out of the following packages: The project will exist out of the following packages:
| Package | State | Developer | | Package | State | Developer |
|---------------|---------|--------------------------------------| |---------------|---------|--------------------------------------|
| api | started | [@anthdm](https://github.com/anthdm) |
| core | started | [@anthdm](https://github.com/anthdm) | | core | started | [@anthdm](https://github.com/anthdm) |
| network | started | [@anthdm](https://github.com/anthdm) | | network | started | [@anthdm](https://github.com/anthdm) |
| vm | started | [@anthdm](https://github.com/anthdm) | | vm | started | [@anthdm](https://github.com/anthdm) |
| smartcontract | started | [@revett](https://github.com/revett) | | compiler | started | [@anthdm](https://github.com/anthdm) |
| client | started | [@revett](https://github.com/revett) |
| cli | started | [@revett](https://github.com/revett) | | cli | started | [@revett](https://github.com/revett) |
| wallet | started | [@pawanrawal](https://github.com/pawanrawal) |
# Getting Started # Getting Started
## Server ## Node
Install dependencies, this requires [dep](https://github.com/golang/dep): Install dependencies, this requires [dep](https://github.com/golang/dep):
@ -112,7 +110,7 @@ TODO
``` ```
## Smart Contracts ## Smart Contracts
> NOTE: At this moment there is only a small subset of the Go language implemented. > In depth documentation about the neo-go compiler and smart contract examples can be found inside the [compiler package](https://github.com/CityOfZion/neo-go/tree/master/pkg/vm/compiler).
### Compile a smart contract ### Compile a smart contract

View file

@ -1 +1 @@
0.16.0 0.17.0

View file

@ -1,36 +1,42 @@
package runtime package runtime
import "github.com/CityOfZion/neo-go/pkg/core" import "github.com/CityOfZion/neo-go/pkg/smartcontract/types"
// T is shorthand for interface{} which allows us to use the function signatures
// in a more elegant way.
type T interface{}
// GetTrigger return the current trigger type. The return in this function
// doesn't really mather, this is just an interop placeholder.
func GetTrigger() T { return 0 }
// CheckWitness verifies if the invoker is the owner of the contract. // CheckWitness verifies if the invoker is the owner of the contract.
func CheckWitness(hash T) T { return 0 } func CheckWitness(hash []byte) bool {
return true
}
// GetCurrentBlock returns the current block. // GetCurrentBlock returns the current block.
func GetCurrentBlock() core.Block { return core.Block{} } func GetCurrentBlock() types.Block { return types.Block{} }
// GetTime returns the timestamp of the most recent block. // GetTime returns the timestamp of the most recent block.
func GetTime() int { return 0 } func GetTime() int {
return 0
}
// Notify an event to the VM. // Notify an event to the VM.
func Notify(arg T) T { return 0 } func Notify(arg interface{}) int {
return 0
}
// Log intructs the VM to log the given message. // Log intructs the VM to log the given message.
func Log(message string) T { return 0 } func Log(message string) int {
return 0
// Verification returns the verification trigger type.
func Verification() byte {
return 0x00
} }
// Application returns the application trigger type. // Application returns the application trigger type.
func Application() byte { func Application() byte {
return 0x10 return 0x10
} }
// Verification returns the verification trigger type.
func Verification() byte {
return 0x00
}
// GetTrigger return the current trigger type. The return in this function
// doesn't really mather, this is just an interop placeholder.
func GetTrigger() interface{} {
return 0
}

View file

@ -0,0 +1,9 @@
package types
// Block represents a block in the blockchain.
type Block struct{}
// Index returns the height of the block.
func (b Block) Index() int {
return 0
}

View file

@ -2,13 +2,9 @@
The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand. The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand.
> The neo-go compiler is under very active development and will be updated on a weekly basis. ***NOTE:*** The neo-go compiler is under very active development and will be updated on a weekly basis. The API is likely going to chance, ***hence do not use this in production environments (mainnet)*** yet.
## Usage For help, questions and discussion feel free to join the [City Of Zion discord](https://discordapp.com/invite/R8v48YA) and hop in the #golang channel. Or reach out to me on twitter [@anthdm](https://twitter.com/anthdm)
```
./bin/neo-go contract compile -i mycontract.go --out /Users/foo/bar/contract.avm
```
## Currently supported ## Currently supported
@ -19,7 +15,7 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
- types int, string, byte and booleans - types int, string, byte and booleans
- struct types + method receives - struct types + method receives
- functions - functions
- composite literals `[]int, []string` - composite literals `[]int, []string, []byte`
- basic if statements - basic if statements
- binary expressions. - binary expressions.
- return statements - return statements
@ -41,6 +37,87 @@ Due to the limitations of the NEO virtual machine, features listed below will no
- goroutines - goroutines
- multiple returns - multiple returns
## Smart contract examples
### Check if the invoker of the contract is the owning address
```Golang
package mycontract
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/runtime"
var owner = []byte{0xaf, 0x12, 0xa8, 0x68, 0x7b, 0x14, 0x94, 0x8b, 0xc4, 0xa0, 0x08, 0x12, 0x8a, 0x55, 0x0a, 0x63, 0x69, 0x5b, 0xc1, 0xa5}
func Main() bool {
isOwner := runtime.CheckWitness(owner)
if isOwner {
runtime.Log("invoker is the owner")
return true
}
return false
}
```
### Simple token
```Golang
package mytoken
import (
"github.com/CityOfZion/neo-go/pkg/smartcontract/runtime"
"github.com/CityOfZion/neo-go/pkg/smartcontract/storage"
)
var owner = []byte{0xaf, 0x12, 0xa8, 0x68, 0x7b, 0x14, 0x94, 0x8b, 0xc4, 0xa0, 0x08, 0x12, 0x8a, 0x55, 0x0a, 0x63, 0x69, 0x5b, 0xc1, 0xa5}
type Token struct {
Name string
Symbol string
TotalSupply int
Owner []byte
}
func (t Token) AddToCirculation(amount int) bool {
ctx := storage.GetContext()
inCirc := storage.GetInt(ctx, "in_circ")
inCirc += amount
storage.Put(ctx, "in_circ", inCirc)
return true
}
func newToken() Token {
return Token{
Name: "your awesome NEO token",
Symbol: "YANT",
TotalSupply: 1000,
Owner: owner,
}
}
func Main(operation string, args []interface{}) bool {
token := newToken()
trigger := runtime.GetTrigger()
if trigger == runtime.Verification() {
isOwner := runtime.CheckWitness(token.Owner)
if isOwner {
return true
}
return false
}
if trigger == runtime.Application() {
if operation == "mintTokens" {
token.AddToCirculation(100)
}
}
return true
}
```
## How to report compiler bugs ## How to report compiler bugs
1. Make a proper testcase (example testcases can be found in the tests folder) 1. Make a proper testcase (example testcases can be found in the tests folder)
2. Create an issue on Github 2. Create an issue on Github

View file

@ -9,6 +9,33 @@ import (
"golang.org/x/tools/go/loader" "golang.org/x/tools/go/loader"
) )
// typeAndValueForField returns a zero initializd typeAndValue or the given type.Var.
func typeAndValueForField(fld *types.Var) types.TypeAndValue {
switch t := fld.Type().(type) {
case *types.Basic:
switch t.Kind() {
case types.Int:
return types.TypeAndValue{
Type: t,
Value: constant.MakeInt64(0),
}
case types.String:
return types.TypeAndValue{
Type: t,
Value: constant.MakeString(""),
}
case types.Bool, types.UntypedBool:
return types.TypeAndValue{
Type: t,
Value: constant.MakeBool(false),
}
default:
log.Fatalf("could not initialize struct field %s to zero, type: %s", fld.Name(), t)
}
}
return types.TypeAndValue{}
}
// countGlobals counts the global variables in the program to add // countGlobals counts the global variables in the program to add
// them with the stacksize of the function. // them with the stacksize of the function.
func countGlobals(f *ast.File) (i int64) { func countGlobals(f *ast.File) (i int64) {
@ -69,3 +96,59 @@ func resolveEntryPoint(entry string, pkg *loader.PackageInfo) (*ast.FuncDecl, *a
} }
return main, file return main, file
} }
// indexOfStruct will return the index of the given field inside that struct.
// If the struct does not contain that field it will return -1.
func indexOfStruct(strct *types.Struct, fldName string) int {
for i := 0; i < strct.NumFields(); i++ {
if strct.Field(i).Name() == fldName {
return i
}
}
return -1
}
type funcUsage map[string]bool
func (f funcUsage) funcUsed(name string) bool {
_, ok := f[name]
return ok
}
func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
usage := funcUsage{}
for _, pkg := range pkgs {
for _, f := range pkg.Files {
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.CallExpr:
switch t := n.Fun.(type) {
case *ast.Ident:
usage[t.Name] = true
case *ast.SelectorExpr:
usage[t.Sel.Name] = true
}
}
return true
})
}
}
return usage
}
func isByteArray(lit *ast.CompositeLit, tInfo *types.Info) bool {
if len(lit.Elts) == 0 {
return false
}
typ := tInfo.Types[lit.Elts[0]].Type.Underlying()
switch t := typ.(type) {
case *types.Basic:
switch t.Kind() {
case types.Byte:
return true
}
}
return false
}

View file

@ -89,13 +89,6 @@ func (c *codegen) emitLoadLocal(name string) {
emitOpcode(c.prog, vm.Opickitem) emitOpcode(c.prog, vm.Opickitem)
} }
func (c *codegen) emitLoadStructField(sName, fName string) {
strct := c.scope.loadStruct(sName)
pos := strct.loadField(fName)
emitInt(c.prog, int64(pos))
emitOpcode(c.prog, vm.Opickitem)
}
func (c *codegen) emitStoreLocal(pos int) { func (c *codegen) emitStoreLocal(pos int) {
emitOpcode(c.prog, vm.Ofromaltstack) emitOpcode(c.prog, vm.Ofromaltstack)
emitOpcode(c.prog, vm.Odup) emitOpcode(c.prog, vm.Odup)
@ -111,10 +104,13 @@ func (c *codegen) emitStoreLocal(pos int) {
emitOpcode(c.prog, vm.Osetitem) emitOpcode(c.prog, vm.Osetitem)
} }
func (c *codegen) emitStoreStructField(sName, fName string) { func (c *codegen) emitLoadStructField(i int) {
strct := c.scope.loadStruct(sName) emitInt(c.prog, int64(i))
pos := strct.loadField(fName) emitOpcode(c.prog, vm.Opickitem)
emitInt(c.prog, int64(pos)) }
func (c *codegen) emitStoreStructField(i int) {
emitInt(c.prog, int64(i))
emitOpcode(c.prog, vm.Orot) emitOpcode(c.prog, vm.Orot)
emitOpcode(c.prog, vm.Osetitem) emitOpcode(c.prog, vm.Osetitem)
} }
@ -178,11 +174,11 @@ func (c *codegen) convertFuncDecl(file *ast.File, decl *ast.FuncDecl) {
if decl.Recv != nil { if decl.Recv != nil {
for _, arg := range decl.Recv.List { for _, arg := range decl.Recv.List {
ident := arg.Names[0] ident := arg.Names[0]
t, ok := c.typeInfo.Defs[ident].Type().Underlying().(*types.Struct) // Currently only method receives for struct types is supported.
_, ok := c.typeInfo.Defs[ident].Type().Underlying().(*types.Struct)
if !ok { if !ok {
log.Fatal("method receiver is not a struct type") log.Fatal("method receives for non-struct types is not yet supported")
} }
c.scope.newStruct(t)
l := c.scope.newLocal(ident.Name) l := c.scope.newLocal(ident.Name)
c.emitStoreLocal(l) c.emitStoreLocal(l)
} }
@ -235,12 +231,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
switch n.Tok { switch n.Tok {
case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN: case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN:
c.emitLoadLocal(t.Name) c.emitLoadLocal(t.Name)
ast.Walk(c, n.Rhs[0]) ast.Walk(c, n.Rhs[0]) // can only add assign to 1 expr on the RHS
c.convertToken(n.Tok) c.convertToken(n.Tok)
l := c.scope.loadLocal(t.Name) l := c.scope.loadLocal(t.Name)
c.emitStoreLocal(l) c.emitStoreLocal(l)
default: default:
ast.Walk(c, n.Rhs[0]) ast.Walk(c, n.Rhs[i])
l := c.scope.loadLocal(t.Name) l := c.scope.loadLocal(t.Name)
c.emitStoreLocal(l) c.emitStoreLocal(l)
} }
@ -249,8 +245,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
switch expr := t.X.(type) { switch expr := t.X.(type) {
case *ast.Ident: case *ast.Ident:
ast.Walk(c, n.Rhs[i]) ast.Walk(c, n.Rhs[i])
c.emitLoadLocal(expr.Name) // load the struct typ := c.typeInfo.ObjectOf(expr).Type().Underlying()
c.emitStoreStructField(expr.Name, t.Sel.Name) // store the field if strct, ok := typ.(*types.Struct); ok {
c.emitLoadLocal(expr.Name) // load the struct
i := indexOfStruct(strct, t.Sel.Name) // get the index of the field
c.emitStoreStructField(i) // store the field
}
default: default:
log.Fatal("nested selector assigns not supported yet") log.Fatal("nested selector assigns not supported yet")
} }
@ -320,6 +320,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
typ = c.typeInfo.ObjectOf(t.Sel).Type().Underlying() typ = c.typeInfo.ObjectOf(t.Sel).Type().Underlying()
default: default:
ln := len(n.Elts) ln := len(n.Elts)
// ByteArrays need a different approach then normal arrays.
if isByteArray(n, c.typeInfo) {
c.convertByteArray(n)
return nil
}
for i := ln - 1; i >= 0; i-- { for i := ln - 1; i >= 0; i-- {
c.emitLoadConst(c.typeInfo.Types[n.Elts[i]]) c.emitLoadConst(c.typeInfo.Types[n.Elts[i]])
} }
@ -430,17 +435,18 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
switch t := n.X.(type) { switch t := n.X.(type) {
case *ast.Ident: case *ast.Ident:
typ := c.typeInfo.ObjectOf(t).Type().Underlying() typ := c.typeInfo.ObjectOf(t).Type().Underlying()
switch typ.(type) { if strct, ok := typ.(*types.Struct); ok {
case *types.Struct: c.emitLoadLocal(t.Name) // load the struct
c.emitLoadLocal(t.Name) // load the struct i := indexOfStruct(strct, n.Sel.Name)
c.emitLoadStructField(t.Name, n.Sel.Name) // load the field c.emitLoadStructField(i) // load the field
default:
log.Fatal("non struct import selections not yet implemented, please use functions instead")
} }
default: default:
log.Fatal("nested selectors not supported yet") log.Fatal("nested selectors not supported yet")
} }
return nil return nil
case *ast.UnaryExpr:
// fmt.Println(n)
} }
return c return c
} }
@ -454,24 +460,33 @@ func (c *codegen) convertSyscall(name string) {
emitOpcode(c.prog, vm.Onop) emitOpcode(c.prog, vm.Onop)
} }
func (c *codegen) convertByteArray(lit *ast.CompositeLit) {
buf := make([]byte, len(lit.Elts))
for i := 0; i < len(lit.Elts); i++ {
t := c.typeInfo.Types[lit.Elts[i]]
val, _ := constant.Int64Val(t.Value)
buf[i] = byte(val)
}
emitBytes(c.prog, buf)
}
func (c *codegen) convertStruct(lit *ast.CompositeLit) { func (c *codegen) convertStruct(lit *ast.CompositeLit) {
// Create a new structScope to initialize and store // Create a new structScope to initialize and store
// the positions of its variables. // the positions of its variables.
t, ok := c.typeInfo.TypeOf(lit).Underlying().(*types.Struct) strct, ok := c.typeInfo.TypeOf(lit).Underlying().(*types.Struct)
if !ok { if !ok {
log.Fatalf("the given literal is not of type struct: %v", lit) log.Fatalf("the given literal is not of type struct: %v", lit)
} }
strct := c.scope.newStruct(t)
emitOpcode(c.prog, vm.Onop) emitOpcode(c.prog, vm.Onop)
emitInt(c.prog, int64(strct.t.NumFields())) emitInt(c.prog, int64(strct.NumFields()))
emitOpcode(c.prog, vm.Onewstruct) emitOpcode(c.prog, vm.Onewstruct)
emitOpcode(c.prog, vm.Otoaltstack) emitOpcode(c.prog, vm.Otoaltstack)
// We need to locally store all the fields, even if they are not initialized. // We need to locally store all the fields, even if they are not initialized.
// We will initialize all fields to their "zero" value. // We will initialize all fields to their "zero" value.
for i := 0; i < strct.t.NumFields(); i++ { for i := 0; i < strct.NumFields(); i++ {
sField := strct.t.Field(i) sField := strct.Field(i)
fieldAdded := false fieldAdded := false
// Fields initialized by the program. // Fields initialized by the program.
@ -481,7 +496,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) {
if sField.Name() == fieldName { if sField.Name() == fieldName {
ast.Walk(c, f.Value) ast.Walk(c, f.Value)
pos := strct.loadField(fieldName) pos := indexOfStruct(strct, fieldName)
c.emitStoreLocal(pos) c.emitStoreLocal(pos)
fieldAdded = true fieldAdded = true
break break
@ -490,7 +505,9 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) {
if fieldAdded { if fieldAdded {
continue continue
} }
c.emitLoadConst(strct.typeAndValues[sField.Name()])
typeAndVal := typeAndValueForField(sField)
c.emitLoadConst(typeAndVal)
c.emitStoreLocal(i) c.emitStoreLocal(i)
} }
emitOpcode(c.prog, vm.Ofromaltstack) emitOpcode(c.prog, vm.Ofromaltstack)
@ -552,6 +569,8 @@ func CodeGen(info *buildInfo) (*bytes.Buffer, error) {
log.Fatal("could not find func main. did you forgot to declare it?") log.Fatal("could not find func main. did you forgot to declare it?")
} }
funUsage := analyzeFuncUsage(info.program.AllPackages)
// Bring all imported functions into scope // Bring all imported functions into scope
for _, pkg := range info.program.AllPackages { for _, pkg := range info.program.AllPackages {
for _, f := range pkg.Files { for _, f := range pkg.Files {
@ -565,11 +584,14 @@ func CodeGen(info *buildInfo) (*bytes.Buffer, error) {
// Generate the code for the program // Generate the code for the program
for _, pkg := range info.program.AllPackages { for _, pkg := range info.program.AllPackages {
c.typeInfo = &pkg.Info c.typeInfo = &pkg.Info
for _, f := range pkg.Files { for _, f := range pkg.Files {
for _, decl := range f.Decls { for _, decl := range f.Decls {
switch n := decl.(type) { switch n := decl.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
if n.Name.Name != mainIdent { // Dont convert the function if its not used. This will save alot
// of bytecode space.
if n.Name.Name != mainIdent && funUsage.funcUsed(n.Name.Name) {
c.convertFuncDecl(f, n) c.convertFuncDecl(f, n)
} }
} }

View file

@ -6,15 +6,12 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/importer"
"go/parser" "go/parser"
"go/token"
"go/types" "go/types"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path/filepath"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
@ -73,40 +70,6 @@ type archive struct {
typeInfo *types.Info typeInfo *types.Info
} }
func resolveImports(f *ast.File) (map[string]*archive, error) {
packages := map[string]*archive{}
for _, imp := range f.Imports {
path := strings.Replace(imp.Path.Value, `"`, "", 2)
path = filepath.Join(gopath(), "src", path)
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, path, nil, 0)
if err != nil {
return nil, err
}
for name, pkg := range pkgs {
file := ast.MergePackageFiles(pkg, 0)
conf := types.Config{Importer: importer.Default()}
typeInfo := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Scopes: make(map[ast.Node]*types.Scope),
}
// Typechecker
_, err = conf.Check("", fset, []*ast.File{file}, typeInfo)
if err != nil {
return nil, err
}
packages[name] = &archive{file, typeInfo}
}
}
return packages, nil
}
// CompileAndSave will compile and save the file to disk. // CompileAndSave will compile and save the file to disk.
func CompileAndSave(src string, o *Options) error { func CompileAndSave(src string, o *Options) error {
if !strings.HasSuffix(src, ".go") { if !strings.HasSuffix(src, ".go") {

View file

@ -2,8 +2,6 @@ package compiler
import ( import (
"go/ast" "go/ast"
"go/types"
"log"
) )
// A funcScope represents the scope within the function context. // A funcScope represents the scope within the function context.
@ -21,9 +19,6 @@ type funcScope struct {
// Local variables // Local variables
locals map[string]int locals map[string]int
// A mapping of structs positions with their scope
structs map[int]*structScope
// voidCalls are basically functions that return their value // voidCalls are basically functions that return their value
// into nothing. The stack has their return value but there // into nothing. The stack has their return value but there
// is nothing that consumes it. We need to keep track of // is nothing that consumes it. We need to keep track of
@ -42,7 +37,6 @@ func newFuncScope(decl *ast.FuncDecl, label int) *funcScope {
decl: decl, decl: decl,
label: label, label: label,
locals: map[string]int{}, locals: map[string]int{},
structs: map[int]*structScope{},
voidCalls: map[*ast.CallExpr]bool{}, voidCalls: map[*ast.CallExpr]bool{},
i: -1, i: -1,
} }
@ -77,7 +71,9 @@ func (c *funcScope) stackSize() int64 {
size := 0 size := 0
ast.Inspect(c.decl, func(n ast.Node) bool { ast.Inspect(c.decl, func(n ast.Node) bool {
switch n := n.(type) { switch n := n.(type) {
case *ast.AssignStmt, *ast.ReturnStmt, *ast.IfStmt: case *ast.AssignStmt:
size += len(n.Rhs)
case *ast.ReturnStmt, *ast.IfStmt:
size++ size++
// This handles the inline GenDecl like "var x = 2" // This handles the inline GenDecl like "var x = 2"
case *ast.GenDecl: case *ast.GenDecl:
@ -99,21 +95,6 @@ func (c *funcScope) stackSize() int64 {
return int64(size + numArgs + len(c.voidCalls)) return int64(size + numArgs + len(c.voidCalls))
} }
func (c *funcScope) newStruct(t *types.Struct) *structScope {
strct := newStructScope(t)
c.structs[len(c.locals)] = strct
return strct
}
func (c *funcScope) loadStruct(name string) *structScope {
l := c.loadLocal(name)
strct, ok := c.structs[l]
if !ok {
log.Fatalf("could not resolve struct %s", name)
}
return strct
}
// newLocal creates a new local variable into the scope of the function. // newLocal creates a new local variable into the scope of the function.
func (c *funcScope) newLocal(name string) int { func (c *funcScope) newLocal(name string) int {
c.i++ c.i++

View file

@ -1,89 +0,0 @@
package compiler
import (
"go/constant"
"go/types"
"log"
)
// A structScope holds the positions for it's fields. Struct fields have different
// positions then local variables in any scope.
type structScope struct {
// A pointer to the underlying type.
t *types.Struct
// A mapping of fieldnames identifier and its position.
fields map[string]int
// A mapping of fieldnames and with type and value.
// This will be populated in "initFields" to initialize all
// structs fields to their zero value.
// strings: "" (just a pushf 0x00)
// int: 0
// bool: false
typeAndValues map[string]types.TypeAndValue
}
// newStructScope will create a new structScope with all fields initialized.
func newStructScope(t *types.Struct) *structScope {
s := &structScope{
fields: map[string]int{},
typeAndValues: make(map[string]types.TypeAndValue, t.NumFields()),
t: t,
}
s.initFields()
return s
}
func (s *structScope) initFields() {
var tv types.TypeAndValue
for i := 0; i < s.t.NumFields(); i++ {
f := s.t.Field(i)
s.newField(f.Name())
switch t := f.Type().(type) {
case *types.Basic:
switch t.Kind() {
case types.Int:
tv = types.TypeAndValue{
Type: t,
Value: constant.MakeInt64(0),
}
case types.String:
tv = types.TypeAndValue{
Type: t,
Value: constant.MakeString(""),
}
case types.Bool, types.UntypedBool:
tv = types.TypeAndValue{
Type: t,
Value: constant.MakeBool(false),
}
default:
log.Fatalf("could not initialize struct field %s to zero, type: %s", f.Name(), t)
}
}
s.typeAndValues[f.Name()] = tv
}
}
func (s *structScope) newField(name string) int {
i := len(s.fields)
s.fields[name] = i
return i
}
func (s *structScope) loadField(name string) int {
i, ok := s.fields[name]
if !ok {
log.Fatalf("could not resolve field %s for struct %v", name, s)
}
return i
}
func (s *structScope) initialize(t *types.Struct) {
s.t = t
for i := 0; i < t.NumFields(); i++ {
s.newField(t.Field(i).Name())
}
}

View file

@ -112,4 +112,15 @@ var assignTestCases = []testCase{
`, `,
"52c56b546c766b00527ac46203006c766b00c3616c7566", "52c56b546c766b00527ac46203006c766b00c3616c7566",
}, },
{
"multi assign",
`
package foo
func Main() int {
x, y := 1, 2
return x + y
}
`,
"53c56b516c766b00527ac4526c766b51527ac46203006c766b00c36c766b51c393616c7566",
},
} }

View file

@ -12,4 +12,46 @@ var boolTestCases = []testCase{
`, `,
"52c56b516c766b00527ac46203006c766b00c3616c7566", "52c56b516c766b00527ac46203006c766b00c3616c7566",
}, },
{
"bool compare",
`
package foo
func Main() int {
x := true
if x {
return 10
}
return 0
}
`,
"54c56b516c766b00527ac46c766b00c3640b006203005a616c756662030000616c7566",
},
{
"bool compare verbose",
`
package foo
func Main() int {
x := true
if x == true {
return 10
}
return 0
}
`,
"54c56b516c766b00527ac46c766b00c3519c640b006203005a616c756662030000616c7566",
},
// {
// "bool invert (unary expr)",
// `
// package foo
// func Main() int {
// x := true
// if !x {
// return 10
// }
// return 0
// }
// `,
// "54c56b516c766b00527ac46c766b00c3630b006203005a616c756662030000616c7566",
// },
} }

View file

@ -37,6 +37,7 @@ func TestAllCases(t *testing.T) {
// Blockchain specific // Blockchain specific
testCases = append(testCases, storageTestCases...) testCases = append(testCases, storageTestCases...)
testCases = append(testCases, runtimeTestCases...)
for _, tc := range testCases { for _, tc := range testCases {
b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{}) b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{})
@ -50,6 +51,7 @@ func TestAllCases(t *testing.T) {
} }
if bytes.Compare(b, expectedResult) != 0 { if bytes.Compare(b, expectedResult) != 0 {
fmt.Println(tc.src)
t.Log(hex.EncodeToString(b)) t.Log(hex.EncodeToString(b))
dumpOpCodeSideBySide(b, expectedResult) dumpOpCodeSideBySide(b, expectedResult)
t.Fatalf("compiling %s failed", tc.name) t.Fatalf("compiling %s failed", tc.name)

View file

@ -71,4 +71,21 @@ var ifStatementTestCases = []testCase{
`, `,
"54c56b5a6c766b00527ac46c766b00c35aa2630e006c766b00c30114a1640b0062030051616c756662030000616c7566", "54c56b5a6c766b00527ac46c766b00c35aa2630e006c766b00c30114a1640b0062030051616c756662030000616c7566",
}, },
{
"nested if statements",
`
package testcase
func Main() int {
x := 10
if x > 10 {
if x < 20 {
return 1
}
return 2
}
return 0
}
`,
"56c56b5a6c766b00527ac46c766b00c35aa0641e006c766b00c301149f640b0062030051616c756662030052616c756662030000616c7566",
},
} }

View file

@ -0,0 +1,99 @@
package compiler
var runtimeTestCases = []testCase{
{
"Notify test",
`
package foo
import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime"
func Main() bool {
runtime.Notify("hello")
return true
}
`,
"52c56b0568656c6c6f6168124e656f2e52756e74696d652e4e6f746966796162030051616c756652c56b6c766b00527ac462030000616c7566",
},
{
"Log test",
`
package foo
import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime"
func Main() bool {
runtime.Log("hello you there!")
return true
}
`,
"52c56b1068656c6c6f20796f752074686572652161680f4e656f2e52756e74696d652e4c6f676162030051616c756652c56b6c766b00527ac462030000616c7566",
},
{
"GetTime test",
`
package foo
import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime"
func Main() int {
t := runtime.GetTime()
return t
}
`,
"52c56b6168134e656f2e52756e74696d652e47657454696d65616c766b00527ac46203006c766b00c3616c756651c56b62030000616c7566",
},
{
"GetTrigger test",
`
package foo
import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime"
func Main() int {
trigger := runtime.GetTrigger()
if trigger == runtime.Application() {
return 1
}
if trigger == runtime.Verification() {
return 2
}
return 0
}
`,
"56c56b6168164e656f2e52756e74696d652e47657454726967676572616c766b00527ac46c766b00c361652c009c640b0062030051616c75666c766b00c3616523009c640b0062030052616c756662030000616c756651c56b6203000110616c756651c56b6203000100616c756651c56b62030000616c7566",
},
{
"check witness",
`
package foo
import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime"
func Main() int {
owner := []byte{0xaf, 0x12, 0xa8, 0x68, 0x7b, 0x14, 0x94, 0x8b, 0xc4, 0xa0, 0x08, 0x12, 0x8a, 0x55, 0x0a, 0x63, 0x69, 0x5b, 0xc1, 0xa5}
isOwner := runtime.CheckWitness(owner)
if isOwner {
return 1
}
return 0
}
`,
"55c56b14af12a8687b14948bc4a008128a550a63695bc1a56c766b00527ac46c766b00c36168184e656f2e52756e74696d652e436865636b5769746e657373616c766b51527ac46c766b51c3640b0062030051616c756662030000616c756652c56b6c766b00527ac462030000616c7566",
},
{
"getCurrentBlock",
`
package foo
import "github.com/CityOfZion/neo-go/pkg/smartcontract/runtime"
func Main() int {
block := runtime.GetCurrentBlock()
runtime.Notify(block)
return 0
}
`,
"53c56b61681b4e656f2e52756e74696d652e47657443757272656e74426c6f636b616c766b00527ac46c766b00c36168124e656f2e52756e74696d652e4e6f746966796162030000616c756651c56b62030000616c756652c56b6c766b00527ac462030000616c7566",
},
}

View file

@ -15,6 +15,6 @@ var storageTestCases = []testCase{
return amount return amount
} }
`, `,
"54c56b6168164e656f2e53746f726167652e476574436f6e74657874616c766b00527ac46c766b00c306616d6f756e7402e803527261680f4e656f2e53746f726167652e507574616c766b00c306616d6f756e747c61680f4e656f2e53746f726167652e476574616c766b51527ac46203006c766b51c3616c756651c56b62030000616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c7566", "54c56b6168164e656f2e53746f726167652e476574436f6e74657874616c766b00527ac46c766b00c306616d6f756e7402e803527261680f4e656f2e53746f726167652e507574616c766b00c306616d6f756e747c61680f4e656f2e53746f726167652e476574616c766b51527ac46203006c766b51c3616c756651c56b62030000616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c7566",
}, },
} }

View file

@ -200,4 +200,29 @@ var structTestCases = []testCase{
`, `,
"51c56b62030061650700616c756651c56b6203006154c66b516c766b00527ac4526c766b51527ac40568656c6c6f6c766b52527ac4006c766b53527ac46c616c7566", "51c56b62030061650700616c756651c56b6203006154c66b516c766b00527ac4526c766b51527ac40568656c6c6f6c766b52527ac4006c766b53527ac46c616c7566",
}, },
{
"pass struct as argument",
`
package foo
type Bar struct {
amount int
}
func addToAmount(x int, bar Bar) int {
bar.amount = bar.amount + x
return bar.amount
}
func Main() int {
b := Bar{
amount: 10,
}
x := addToAmount(4, b)
return x
}
`,
"53c56b6151c66b5a6c766b00527ac46c6c766b00527ac4546c766b00c37c616516006c766b51527ac46203006c766b51c3616c756654c56b6c766b00527ac46c766b51527ac46c766b51c300c36c766b00c3936c766b51c3007bc46203006c766b51c300c3616c7566",
},
} }

View file

@ -15,6 +15,6 @@ var Syscalls = map[string]string{
"CheckWitness": "Neo.Runtime.CheckWitness", "CheckWitness": "Neo.Runtime.CheckWitness",
"GetCurrentBlock": "Neo.Runtime.GetCurrentBlock", "GetCurrentBlock": "Neo.Runtime.GetCurrentBlock",
"GetTime": "Neo.Runtime.GetTime", "GetTime": "Neo.Runtime.GetTime",
"Notify": "Neo.runtime.Notify", "Notify": "Neo.Runtime.Notify",
"Log": "Neo.Runtime.Log", "Log": "Neo.Runtime.Log",
} }