forked from TrueCloudLab/neoneo-go
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:
parent
de3395fb51
commit
2345858238
19 changed files with 464 additions and 215 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -22,3 +22,6 @@ bin/
|
|||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# anthdm todolists
|
||||
/pkg/vm/compiler/todo.md
|
||||
|
|
12
README.md
12
README.md
|
@ -40,23 +40,21 @@ A complete toolkit for the NEO blockchain, including:
|
|||
|
||||
## 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:
|
||||
|
||||
| Package | State | Developer |
|
||||
|---------------|---------|--------------------------------------|
|
||||
| api | started | [@anthdm](https://github.com/anthdm) |
|
||||
| core | started | [@anthdm](https://github.com/anthdm) |
|
||||
| network | 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) |
|
||||
| wallet | started | [@pawanrawal](https://github.com/pawanrawal) |
|
||||
|
||||
# Getting Started
|
||||
|
||||
## Server
|
||||
## Node
|
||||
|
||||
Install dependencies, this requires [dep](https://github.com/golang/dep):
|
||||
|
||||
|
@ -112,7 +110,7 @@ TODO
|
|||
```
|
||||
|
||||
## 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
|
||||
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.16.0
|
||||
0.17.0
|
||||
|
|
|
@ -1,36 +1,42 @@
|
|||
package runtime
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/core"
|
||||
|
||||
// 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 }
|
||||
import "github.com/CityOfZion/neo-go/pkg/smartcontract/types"
|
||||
|
||||
// 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.
|
||||
func GetCurrentBlock() core.Block { return core.Block{} }
|
||||
func GetCurrentBlock() types.Block { return types.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.
|
||||
func Notify(arg T) T { return 0 }
|
||||
func Notify(arg interface{}) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Log intructs the VM to log the given message.
|
||||
func Log(message string) T { return 0 }
|
||||
|
||||
// Verification returns the verification trigger type.
|
||||
func Verification() byte {
|
||||
return 0x00
|
||||
func Log(message string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Application returns the application trigger type.
|
||||
func Application() byte {
|
||||
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
|
||||
}
|
||||
|
|
9
pkg/smartcontract/types/block.go
Normal file
9
pkg/smartcontract/types/block.go
Normal 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
|
||||
}
|
|
@ -2,13 +2,9 @@
|
|||
|
||||
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
|
||||
|
||||
```
|
||||
./bin/neo-go contract compile -i mycontract.go --out /Users/foo/bar/contract.avm
|
||||
```
|
||||
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)
|
||||
|
||||
## 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
|
||||
- struct types + method receives
|
||||
- functions
|
||||
- composite literals `[]int, []string`
|
||||
- composite literals `[]int, []string, []byte`
|
||||
- basic if statements
|
||||
- binary expressions.
|
||||
- return statements
|
||||
|
@ -41,6 +37,87 @@ Due to the limitations of the NEO virtual machine, features listed below will no
|
|||
- goroutines
|
||||
- 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
|
||||
1. Make a proper testcase (example testcases can be found in the tests folder)
|
||||
2. Create an issue on Github
|
||||
|
|
|
@ -9,6 +9,33 @@ import (
|
|||
"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
|
||||
// them with the stacksize of the function.
|
||||
func countGlobals(f *ast.File) (i int64) {
|
||||
|
@ -69,3 +96,59 @@ func resolveEntryPoint(entry string, pkg *loader.PackageInfo) (*ast.FuncDecl, *a
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -89,13 +89,6 @@ func (c *codegen) emitLoadLocal(name string) {
|
|||
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) {
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
emitOpcode(c.prog, vm.Odup)
|
||||
|
@ -111,10 +104,13 @@ func (c *codegen) emitStoreLocal(pos int) {
|
|||
emitOpcode(c.prog, vm.Osetitem)
|
||||
}
|
||||
|
||||
func (c *codegen) emitStoreStructField(sName, fName string) {
|
||||
strct := c.scope.loadStruct(sName)
|
||||
pos := strct.loadField(fName)
|
||||
emitInt(c.prog, int64(pos))
|
||||
func (c *codegen) emitLoadStructField(i int) {
|
||||
emitInt(c.prog, int64(i))
|
||||
emitOpcode(c.prog, vm.Opickitem)
|
||||
}
|
||||
|
||||
func (c *codegen) emitStoreStructField(i int) {
|
||||
emitInt(c.prog, int64(i))
|
||||
emitOpcode(c.prog, vm.Orot)
|
||||
emitOpcode(c.prog, vm.Osetitem)
|
||||
}
|
||||
|
@ -178,11 +174,11 @@ func (c *codegen) convertFuncDecl(file *ast.File, decl *ast.FuncDecl) {
|
|||
if decl.Recv != nil {
|
||||
for _, arg := range decl.Recv.List {
|
||||
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 {
|
||||
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)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
|
@ -235,12 +231,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
switch n.Tok {
|
||||
case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN:
|
||||
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)
|
||||
l := c.scope.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
default:
|
||||
ast.Walk(c, n.Rhs[0])
|
||||
ast.Walk(c, n.Rhs[i])
|
||||
l := c.scope.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
|
@ -249,8 +245,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
switch expr := t.X.(type) {
|
||||
case *ast.Ident:
|
||||
ast.Walk(c, n.Rhs[i])
|
||||
c.emitLoadLocal(expr.Name) // load the struct
|
||||
c.emitStoreStructField(expr.Name, t.Sel.Name) // store the field
|
||||
typ := c.typeInfo.ObjectOf(expr).Type().Underlying()
|
||||
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:
|
||||
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()
|
||||
default:
|
||||
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-- {
|
||||
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) {
|
||||
case *ast.Ident:
|
||||
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||
switch typ.(type) {
|
||||
case *types.Struct:
|
||||
c.emitLoadLocal(t.Name) // load the struct
|
||||
c.emitLoadStructField(t.Name, n.Sel.Name) // load the field
|
||||
default:
|
||||
log.Fatal("non struct import selections not yet implemented, please use functions instead")
|
||||
if strct, ok := typ.(*types.Struct); ok {
|
||||
c.emitLoadLocal(t.Name) // load the struct
|
||||
i := indexOfStruct(strct, n.Sel.Name)
|
||||
c.emitLoadStructField(i) // load the field
|
||||
}
|
||||
default:
|
||||
log.Fatal("nested selectors not supported yet")
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
// fmt.Println(n)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
@ -454,24 +460,33 @@ func (c *codegen) convertSyscall(name string) {
|
|||
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) {
|
||||
// Create a new structScope to initialize and store
|
||||
// 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 {
|
||||
log.Fatalf("the given literal is not of type struct: %v", lit)
|
||||
}
|
||||
strct := c.scope.newStruct(t)
|
||||
|
||||
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.Otoaltstack)
|
||||
|
||||
// We need to locally store all the fields, even if they are not initialized.
|
||||
// We will initialize all fields to their "zero" value.
|
||||
for i := 0; i < strct.t.NumFields(); i++ {
|
||||
sField := strct.t.Field(i)
|
||||
for i := 0; i < strct.NumFields(); i++ {
|
||||
sField := strct.Field(i)
|
||||
fieldAdded := false
|
||||
|
||||
// Fields initialized by the program.
|
||||
|
@ -481,7 +496,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
|||
|
||||
if sField.Name() == fieldName {
|
||||
ast.Walk(c, f.Value)
|
||||
pos := strct.loadField(fieldName)
|
||||
pos := indexOfStruct(strct, fieldName)
|
||||
c.emitStoreLocal(pos)
|
||||
fieldAdded = true
|
||||
break
|
||||
|
@ -490,7 +505,9 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
|||
if fieldAdded {
|
||||
continue
|
||||
}
|
||||
c.emitLoadConst(strct.typeAndValues[sField.Name()])
|
||||
|
||||
typeAndVal := typeAndValueForField(sField)
|
||||
c.emitLoadConst(typeAndVal)
|
||||
c.emitStoreLocal(i)
|
||||
}
|
||||
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?")
|
||||
}
|
||||
|
||||
funUsage := analyzeFuncUsage(info.program.AllPackages)
|
||||
|
||||
// Bring all imported functions into scope
|
||||
for _, pkg := range info.program.AllPackages {
|
||||
for _, f := range pkg.Files {
|
||||
|
@ -565,11 +584,14 @@ func CodeGen(info *buildInfo) (*bytes.Buffer, error) {
|
|||
// Generate the code for the program
|
||||
for _, pkg := range info.program.AllPackages {
|
||||
c.typeInfo = &pkg.Info
|
||||
|
||||
for _, f := range pkg.Files {
|
||||
for _, decl := range f.Decls {
|
||||
switch n := decl.(type) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,12 @@ import (
|
|||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
|
@ -73,40 +70,6 @@ type archive struct {
|
|||
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.
|
||||
func CompileAndSave(src string, o *Options) error {
|
||||
if !strings.HasSuffix(src, ".go") {
|
||||
|
|
|
@ -2,8 +2,6 @@ package compiler
|
|||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"log"
|
||||
)
|
||||
|
||||
// A funcScope represents the scope within the function context.
|
||||
|
@ -21,9 +19,6 @@ type funcScope struct {
|
|||
// Local variables
|
||||
locals map[string]int
|
||||
|
||||
// A mapping of structs positions with their scope
|
||||
structs map[int]*structScope
|
||||
|
||||
// voidCalls are basically functions that return their value
|
||||
// into nothing. The stack has their return value but there
|
||||
// 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,
|
||||
label: label,
|
||||
locals: map[string]int{},
|
||||
structs: map[int]*structScope{},
|
||||
voidCalls: map[*ast.CallExpr]bool{},
|
||||
i: -1,
|
||||
}
|
||||
|
@ -77,7 +71,9 @@ func (c *funcScope) stackSize() int64 {
|
|||
size := 0
|
||||
ast.Inspect(c.decl, func(n ast.Node) bool {
|
||||
switch n := n.(type) {
|
||||
case *ast.AssignStmt, *ast.ReturnStmt, *ast.IfStmt:
|
||||
case *ast.AssignStmt:
|
||||
size += len(n.Rhs)
|
||||
case *ast.ReturnStmt, *ast.IfStmt:
|
||||
size++
|
||||
// This handles the inline GenDecl like "var x = 2"
|
||||
case *ast.GenDecl:
|
||||
|
@ -99,21 +95,6 @@ func (c *funcScope) stackSize() int64 {
|
|||
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.
|
||||
func (c *funcScope) newLocal(name string) int {
|
||||
c.i++
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -112,4 +112,15 @@ var assignTestCases = []testCase{
|
|||
`,
|
||||
"52c56b546c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"multi assign",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x, y := 1, 2
|
||||
return x + y
|
||||
}
|
||||
`,
|
||||
"53c56b516c766b00527ac4526c766b51527ac46203006c766b00c36c766b51c393616c7566",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -12,4 +12,46 @@ var boolTestCases = []testCase{
|
|||
`,
|
||||
"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",
|
||||
// },
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ func TestAllCases(t *testing.T) {
|
|||
|
||||
// Blockchain specific
|
||||
testCases = append(testCases, storageTestCases...)
|
||||
testCases = append(testCases, runtimeTestCases...)
|
||||
|
||||
for _, tc := range testCases {
|
||||
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 {
|
||||
fmt.Println(tc.src)
|
||||
t.Log(hex.EncodeToString(b))
|
||||
dumpOpCodeSideBySide(b, expectedResult)
|
||||
t.Fatalf("compiling %s failed", tc.name)
|
||||
|
|
|
@ -71,4 +71,21 @@ var ifStatementTestCases = []testCase{
|
|||
`,
|
||||
"54c56b5a6c766b00527ac46c766b00c35aa2630e006c766b00c30114a1640b0062030051616c756662030000616c7566",
|
||||
},
|
||||
{
|
||||
"nested if statements",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
if x > 10 {
|
||||
if x < 20 {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"56c56b5a6c766b00527ac46c766b00c35aa0641e006c766b00c301149f640b0062030051616c756662030052616c756662030000616c7566",
|
||||
},
|
||||
}
|
||||
|
|
99
pkg/vm/compiler/tests/runtime_test.go
Normal file
99
pkg/vm/compiler/tests/runtime_test.go
Normal 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",
|
||||
},
|
||||
}
|
|
@ -15,6 +15,6 @@ var storageTestCases = []testCase{
|
|||
return amount
|
||||
}
|
||||
`,
|
||||
"54c56b6168164e656f2e53746f726167652e476574436f6e74657874616c766b00527ac46c766b00c306616d6f756e7402e803527261680f4e656f2e53746f726167652e507574616c766b00c306616d6f756e747c61680f4e656f2e53746f726167652e476574616c766b51527ac46203006c766b51c3616c756651c56b62030000616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c7566",
|
||||
"54c56b6168164e656f2e53746f726167652e476574436f6e74657874616c766b00527ac46c766b00c306616d6f756e7402e803527261680f4e656f2e53746f726167652e507574616c766b00c306616d6f756e747c61680f4e656f2e53746f726167652e476574616c766b51527ac46203006c766b51c3616c756651c56b62030000616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c7566",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -200,4 +200,29 @@ var structTestCases = []testCase{
|
|||
`,
|
||||
"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",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ var Syscalls = map[string]string{
|
|||
"CheckWitness": "Neo.Runtime.CheckWitness",
|
||||
"GetCurrentBlock": "Neo.Runtime.GetCurrentBlock",
|
||||
"GetTime": "Neo.Runtime.GetTime",
|
||||
"Notify": "Neo.runtime.Notify",
|
||||
"Notify": "Neo.Runtime.Notify",
|
||||
"Log": "Neo.Runtime.Log",
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue