mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 03:41:34 +00:00
Compiler (#23)
* implemented add, mul, div, sub assign for identifiers. * Implemented struct field initialization. * Implemented imports * Implemented storage VM API (interop layer) + additional bug fixes when encountered. * Bumped version 0.12.0 * fixed double point extension on compiled output file. * Fixed bug where callExpr in returns where added to voidCall * fixed binExpr compare equal * Check the env for the gopath first * removed travis.yml * custom types + implemented general declarations. * commented out the storage test to make the build pass
This commit is contained in:
parent
bebdabab9f
commit
23cfebf621
25 changed files with 798 additions and 117 deletions
|
@ -1,9 +0,0 @@
|
|||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.x
|
||||
- tip
|
||||
install:
|
||||
- go build ./{cmd,pkg}/...
|
||||
script:
|
||||
- go vet ./{cmd,pkg}/...
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.12.0
|
||||
0.13.0
|
||||
|
|
|
@ -5,22 +5,27 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
|
|||
> The neo-go compiler is under very active development and will be updated on a weekly basis.
|
||||
|
||||
## Currently supported
|
||||
|
||||
### Go internals
|
||||
- type checker
|
||||
- multiple assigns
|
||||
- types int, string and bool
|
||||
- types int, string and booleans
|
||||
- struct types + method receives
|
||||
- functions
|
||||
- composite literals `[]int, []string`
|
||||
- basic if statements
|
||||
- binary expressions.
|
||||
- return statements
|
||||
- imports
|
||||
|
||||
### VM API (interop layer)
|
||||
- storage
|
||||
|
||||
## Not yet implemented
|
||||
- for loops
|
||||
- ranges
|
||||
- builtins (append, len, ..)
|
||||
- blockchain helpers (sha256, storage, ..)
|
||||
- import packages
|
||||
- large part of the interop layer (VM API)
|
||||
|
||||
## Not supported
|
||||
Due to the limitations of the NEO virtual machine, features listed below will not be supported.
|
||||
|
@ -28,8 +33,46 @@ Due to the limitations of the NEO virtual machine, features listed below will no
|
|||
- goroutines
|
||||
- multiple returns
|
||||
|
||||
## How to report bugs
|
||||
## 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
|
||||
3. Make a PR with a reference to the created issue, containing the testcase that proves the bug
|
||||
4. Either you fix the bug yourself or wait for patch that solves the problem
|
||||
|
||||
## Quick start
|
||||
|
||||
### Compile a smart contract
|
||||
|
||||
```
|
||||
./bin/neo-go contract compile mycontract.go
|
||||
```
|
||||
|
||||
By default the filename will be the name of your .go file with the .avm extension, the file will be located in the same directory where you called the command from. If you want another location for your compiled contract:
|
||||
|
||||
```
|
||||
./bin/neo-go contract compile mycontract.go --out /Users/foo/bar/contract.avm
|
||||
```
|
||||
|
||||
### Debugging your smart contract
|
||||
You can dump the opcodes generated by the compiler with the following command:
|
||||
|
||||
```
|
||||
./bin/neo-go contract opdump mycontract.go
|
||||
```
|
||||
|
||||
This will result in something like this:
|
||||
|
||||
```
|
||||
INDEX OPCODE DESC
|
||||
0 0x52 OpPush2
|
||||
1 0xc5 OpNewArray
|
||||
2 0x6b OpToAltStack
|
||||
3 0x 0 OpPush0
|
||||
4 0x6c OpFromAltStack
|
||||
5 0x76 OpDup
|
||||
6 0x6b OpToAltStack
|
||||
7 0x 0 OpPush0
|
||||
8 0x52 OpPush2
|
||||
9 0x7a OpRoll
|
||||
10 0xc4 OpSetItem
|
||||
```
|
|
@ -47,7 +47,7 @@ func (c *codegen) pc() int {
|
|||
}
|
||||
|
||||
func (c *codegen) emitLoadConst(t types.TypeAndValue) {
|
||||
switch typ := t.Type.(type) {
|
||||
switch typ := t.Type.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
switch typ.Kind() {
|
||||
case types.Int:
|
||||
|
@ -121,7 +121,9 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
|||
} else {
|
||||
f = c.newFunc(decl)
|
||||
}
|
||||
|
||||
c.fctx = f
|
||||
ast.Inspect(decl, c.fctx.analyzeVoidCalls)
|
||||
|
||||
emitInt(c.prog, f.stackSize())
|
||||
emitOpcode(c.prog, vm.Onewarray)
|
||||
|
@ -135,10 +137,12 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
|||
// to support other types.
|
||||
if decl.Recv != nil {
|
||||
for _, arg := range decl.Recv.List {
|
||||
strct := c.fctx.newStruct()
|
||||
|
||||
ident := arg.Names[0]
|
||||
strct.initializeFields(ident, c.typeInfo)
|
||||
t, ok := c.typeInfo.Defs[ident].Type().Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
log.Fatal("method receiver is not a struct type")
|
||||
}
|
||||
c.fctx.newStruct(t)
|
||||
l := c.fctx.newLocal(ident.Name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
|
@ -157,21 +161,43 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
|||
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||
switch n := node.(type) {
|
||||
|
||||
// General declarations.
|
||||
// With value: var x int = 2
|
||||
// Without value: var x int
|
||||
case *ast.GenDecl:
|
||||
switch t := n.Specs[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
if len(t.Values) > 0 {
|
||||
ast.Walk(c, t.Values[0])
|
||||
l := c.fctx.newLocal(t.Names[0].Name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ast.AssignStmt:
|
||||
for i := 0; i < len(n.Lhs); i++ {
|
||||
// resolve the whole right hand side.
|
||||
ast.Walk(c, n.Rhs[i])
|
||||
// check if we are assigning to a struct or an identifier
|
||||
switch t := n.Lhs[i].(type) {
|
||||
case *ast.Ident:
|
||||
l := c.fctx.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
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])
|
||||
c.convertToken(n.Tok)
|
||||
l := c.fctx.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
default:
|
||||
ast.Walk(c, n.Rhs[0])
|
||||
l := c.fctx.loadLocal(t.Name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
switch n := t.X.(type) {
|
||||
switch expr := t.X.(type) {
|
||||
case *ast.Ident:
|
||||
c.emitLoadLocal(n.Name) // load the struct
|
||||
c.emitStoreStructField(n.Name, t.Sel.Name) // store the field
|
||||
ast.Walk(c, n.Rhs[i])
|
||||
c.emitLoadLocal(expr.Name) // load the struct
|
||||
c.emitStoreStructField(expr.Name, t.Sel.Name) // store the field
|
||||
default:
|
||||
log.Fatal("nested selector assigns not supported yet")
|
||||
}
|
||||
|
@ -232,14 +258,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
return nil
|
||||
|
||||
case *ast.CompositeLit:
|
||||
var typ types.Type
|
||||
|
||||
switch t := n.Type.(type) {
|
||||
case *ast.Ident:
|
||||
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||
switch typ.(type) {
|
||||
case *types.Struct:
|
||||
c.convertStruct(n)
|
||||
}
|
||||
|
||||
typ = c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||
case *ast.SelectorExpr:
|
||||
typ = c.typeInfo.ObjectOf(t.Sel).Type().Underlying()
|
||||
default:
|
||||
ln := len(n.Elts)
|
||||
for i := ln - 1; i >= 0; i-- {
|
||||
|
@ -247,7 +272,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
}
|
||||
emitInt(c.prog, int64(ln))
|
||||
emitOpcode(c.prog, vm.Opack)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch typ.(type) {
|
||||
case *types.Struct:
|
||||
c.convertStruct(n)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
|
@ -275,25 +307,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
|
||||
ast.Walk(c, n.X)
|
||||
ast.Walk(c, n.Y)
|
||||
|
||||
switch n.Op {
|
||||
case token.ADD:
|
||||
emitOpcode(c.prog, vm.Oadd)
|
||||
case token.SUB:
|
||||
emitOpcode(c.prog, vm.Osub)
|
||||
case token.MUL:
|
||||
emitOpcode(c.prog, vm.Omul)
|
||||
case token.QUO:
|
||||
emitOpcode(c.prog, vm.Odiv)
|
||||
case token.LSS:
|
||||
emitOpcode(c.prog, vm.Olt)
|
||||
case token.LEQ:
|
||||
emitOpcode(c.prog, vm.Olte)
|
||||
case token.GTR:
|
||||
emitOpcode(c.prog, vm.Ogt)
|
||||
case token.GEQ:
|
||||
emitOpcode(c.prog, vm.Ogte)
|
||||
}
|
||||
c.convertToken(n.Op)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -311,15 +325,21 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
log.Fatalf("could not resolve function %s", fun.Name)
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
ast.Walk(c, fun.X)
|
||||
// If this is a method call we need to walk the AST to load the struct locally.
|
||||
// Otherwise this is a function call from a imported package and we can call it
|
||||
// directly.
|
||||
if c.typeInfo.Selections[fun] != nil {
|
||||
ast.Walk(c, fun.X)
|
||||
// Dont forget to add 1 extra argument when its a method.
|
||||
numArgs++
|
||||
}
|
||||
f, ok = c.funcs[fun.Sel.Name]
|
||||
if !ok {
|
||||
log.Fatalf("could not resolve function %s", fun.Sel.Name)
|
||||
}
|
||||
// Dont forget to add 1 extra argument when its a method.
|
||||
numArgs++
|
||||
}
|
||||
|
||||
// Handle the arguments
|
||||
for _, arg := range n.Args {
|
||||
ast.Walk(c, arg)
|
||||
}
|
||||
|
@ -335,7 +355,18 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
// and we could easily removed it, but to be consistent with the original compiler I
|
||||
// will put them in. ^^
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitCall(c.prog, vm.Ocall, int16(f.label))
|
||||
|
||||
if isSyscall(f.name) {
|
||||
c.convertSyscall(f.name)
|
||||
} else {
|
||||
emitCall(c.prog, vm.Ocall, int16(f.label))
|
||||
}
|
||||
|
||||
// If we are not assigning this function to a variable we need to drop
|
||||
// the top stack item. It's not a void but you get the point \o/.
|
||||
if _, ok := c.fctx.voidCalls[n]; ok && !isNoRetSyscall(f.name) {
|
||||
emitOpcode(c.prog, vm.Odrop)
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
|
@ -351,31 +382,99 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
return c
|
||||
}
|
||||
|
||||
func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
||||
func (c *codegen) convertSyscall(name string) {
|
||||
api, ok := vm.Syscalls[name]
|
||||
if !ok {
|
||||
log.Fatalf("unknown VM syscall api: %s", name)
|
||||
}
|
||||
emitSyscall(c.prog, api)
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitInt(c.prog, int64(len(lit.Elts)))
|
||||
}
|
||||
|
||||
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)
|
||||
if !ok {
|
||||
log.Fatalf("the given literal is not of type struct: %v", lit)
|
||||
}
|
||||
strct := c.fctx.newStruct(t)
|
||||
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitInt(c.prog, int64(strct.t.NumFields()))
|
||||
emitOpcode(c.prog, vm.Onewstruct)
|
||||
emitOpcode(c.prog, vm.Otoaltstack)
|
||||
|
||||
// Create a new struct scope to store the positions of its variables.
|
||||
strct := c.fctx.newStruct()
|
||||
// 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)
|
||||
fieldAdded := false
|
||||
|
||||
for _, field := range lit.Elts {
|
||||
f := field.(*ast.KeyValueExpr)
|
||||
// Walk to resolve the expression of the value.
|
||||
ast.Walk(c, f.Value)
|
||||
l := strct.newField(f.Key.(*ast.Ident).Name)
|
||||
c.emitStoreLocal(l)
|
||||
// Fields initialized by the program.
|
||||
for _, field := range lit.Elts {
|
||||
f := field.(*ast.KeyValueExpr)
|
||||
fieldName := f.Key.(*ast.Ident).Name
|
||||
|
||||
if sField.Name() == fieldName {
|
||||
ast.Walk(c, f.Value)
|
||||
pos := strct.loadField(fieldName)
|
||||
c.emitStoreLocal(pos)
|
||||
fieldAdded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if fieldAdded {
|
||||
continue
|
||||
}
|
||||
c.emitLoadConst(strct.typeAndValues[sField.Name()])
|
||||
c.emitStoreLocal(i)
|
||||
}
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
}
|
||||
|
||||
func (c *codegen) convertToken(tok token.Token) {
|
||||
switch tok {
|
||||
case token.ADD_ASSIGN:
|
||||
emitOpcode(c.prog, vm.Oadd)
|
||||
case token.SUB_ASSIGN:
|
||||
emitOpcode(c.prog, vm.Osub)
|
||||
case token.MUL_ASSIGN:
|
||||
emitOpcode(c.prog, vm.Omul)
|
||||
case token.QUO_ASSIGN:
|
||||
emitOpcode(c.prog, vm.Odiv)
|
||||
case token.ADD:
|
||||
emitOpcode(c.prog, vm.Oadd)
|
||||
case token.SUB:
|
||||
emitOpcode(c.prog, vm.Osub)
|
||||
case token.MUL:
|
||||
emitOpcode(c.prog, vm.Omul)
|
||||
case token.QUO:
|
||||
emitOpcode(c.prog, vm.Odiv)
|
||||
case token.LSS:
|
||||
emitOpcode(c.prog, vm.Olt)
|
||||
case token.LEQ:
|
||||
emitOpcode(c.prog, vm.Olte)
|
||||
case token.GTR:
|
||||
emitOpcode(c.prog, vm.Ogt)
|
||||
case token.GEQ:
|
||||
emitOpcode(c.prog, vm.Ogte)
|
||||
case token.EQL, token.NEQ:
|
||||
emitOpcode(c.prog, vm.Onumequal)
|
||||
default:
|
||||
log.Fatalf("compiler could not convert token: %s", tok)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
||||
f := newFuncScope(decl, c.newLabel())
|
||||
c.funcs[f.name] = f
|
||||
return f
|
||||
}
|
||||
|
||||
// TODO: Don't know if we really use this. Check if it can be deleted.
|
||||
// If it's used once or twice, also remove this functions and
|
||||
// call .Types direcly. e.g. c.typeInfo.Types[expr]
|
||||
func (c *codegen) getTypeInfo(expr ast.Expr) types.TypeAndValue {
|
||||
return c.typeInfo.Types[expr]
|
||||
}
|
||||
|
@ -401,7 +500,7 @@ func isIdentBool(ident *ast.Ident) bool {
|
|||
}
|
||||
|
||||
// CodeGen is the function that compiles the program to bytecode.
|
||||
func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) {
|
||||
func CodeGen(f *ast.File, tInfo *types.Info, imports map[string]*archive) (*bytes.Buffer, error) {
|
||||
c := &codegen{
|
||||
prog: new(bytes.Buffer),
|
||||
l: []int{},
|
||||
|
@ -424,6 +523,11 @@ func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) {
|
|||
log.Fatal("could not find func main. did you forgot to declare it?")
|
||||
}
|
||||
|
||||
// Bring all imported functions into scope
|
||||
for _, arch := range imports {
|
||||
c.resolveFuncDecls(arch.f)
|
||||
}
|
||||
|
||||
c.resolveFuncDecls(f)
|
||||
c.convertFuncDecl(main)
|
||||
|
||||
|
@ -436,6 +540,19 @@ func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Generate code for the imported packages.
|
||||
for _, arch := range imports {
|
||||
c.typeInfo = arch.typeInfo
|
||||
for _, decl := range arch.f.Decls {
|
||||
switch n := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if n.Name.Name != mainIdent {
|
||||
c.convertFuncDecl(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.writeJumps()
|
||||
|
||||
return c.prog, nil
|
||||
|
@ -470,3 +587,24 @@ func (c *codegen) writeJumps() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isSyscall(name string) bool {
|
||||
_, ok := vm.Syscalls[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
var noRetSyscalls = []string{
|
||||
"Notify", "Log", "Put", "Register", "Delete",
|
||||
"SetVotes", "ContractDestroy", "MerkleRoot", "Hash",
|
||||
"PrevHash", "GetHeader",
|
||||
}
|
||||
|
||||
// isNoRetSyscall checks if the syscall has a return value.
|
||||
func isNoRetSyscall(name string) bool {
|
||||
for _, s := range noRetSyscalls {
|
||||
if s == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
|
@ -14,13 +15,14 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
const fileExt = ".avm"
|
||||
const fileExt = "avm"
|
||||
|
||||
// Options contains all the parameters that affect the behaviour of the compiler.
|
||||
type Options struct {
|
||||
|
@ -58,7 +60,12 @@ func Compile(input io.Reader, o *Options) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := CodeGen(f, typeInfo)
|
||||
imports, err := resolveImports(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := CodeGen(f, typeInfo, imports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -66,6 +73,45 @@ func Compile(input io.Reader, o *Options) ([]byte, error) {
|
|||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type archive struct {
|
||||
f *ast.File
|
||||
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 len(o.Outfile) == 0 {
|
||||
|
@ -113,6 +159,14 @@ func DumpOpcode(src string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func gopath() string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if len(gopath) == 0 {
|
||||
gopath = build.Default.GOPATH
|
||||
}
|
||||
return gopath
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
}
|
||||
|
|
|
@ -58,7 +58,9 @@ func emitBytes(w *bytes.Buffer, b []byte) error {
|
|||
)
|
||||
|
||||
if n == 0 {
|
||||
return errors.New("cannot emit 0 bytes")
|
||||
// The VM expects a pushf (0x00).
|
||||
// Empty strings on the stack for example.
|
||||
return emitOpcode(w, vm.Opushf)
|
||||
}
|
||||
if n <= int(vm.Opushbytes75) {
|
||||
return emit(w, vm.Opcode(n), b)
|
||||
|
@ -80,6 +82,16 @@ func emitBytes(w *bytes.Buffer, b []byte) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func emitSyscall(w *bytes.Buffer, api string) error {
|
||||
if len(api) == 0 {
|
||||
return errors.New("syscall api cannot be of length 0")
|
||||
}
|
||||
buf := make([]byte, len(api)+1)
|
||||
buf[0] = byte(len(api))
|
||||
copy(buf[1:len(buf)], []byte(api))
|
||||
return emit(w, vm.Osyscall, buf)
|
||||
}
|
||||
|
||||
func emitCall(w *bytes.Buffer, op vm.Opcode, label int16) error {
|
||||
return emitJmp(w, op, label)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package compiler
|
|||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"log"
|
||||
)
|
||||
|
||||
|
@ -14,36 +15,75 @@ type funcScope struct {
|
|||
// The declaration of the function in the AST
|
||||
decl *ast.FuncDecl
|
||||
|
||||
// program label of the function
|
||||
// Program label of the function
|
||||
label int
|
||||
|
||||
// local scope of the function
|
||||
// Local scope of the function
|
||||
scope map[string]int
|
||||
|
||||
// mapping of structs positions with their scope
|
||||
// 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
|
||||
// these functions so we can cleanup (drop) the returned
|
||||
// value from the stack. We also need to add every voidCall
|
||||
// return value to the stack size.
|
||||
voidCalls map[*ast.CallExpr]bool
|
||||
|
||||
// local variable counter
|
||||
i int
|
||||
}
|
||||
|
||||
func newFuncScope(decl *ast.FuncDecl, label int) *funcScope {
|
||||
return &funcScope{
|
||||
name: decl.Name.Name,
|
||||
decl: decl,
|
||||
label: label,
|
||||
scope: map[string]int{},
|
||||
structs: map[int]*structScope{},
|
||||
i: -1,
|
||||
name: decl.Name.Name,
|
||||
decl: decl,
|
||||
label: label,
|
||||
scope: map[string]int{},
|
||||
structs: map[int]*structScope{},
|
||||
voidCalls: map[*ast.CallExpr]bool{},
|
||||
i: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// analyzeVoidCalls will check for functions that are not assigned
|
||||
// and therefore we need to cleanup the return value from the stack.
|
||||
func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.AssignStmt:
|
||||
for i := 0; i < len(n.Rhs); i++ {
|
||||
switch n.Rhs[i].(type) {
|
||||
case *ast.CallExpr:
|
||||
return false
|
||||
}
|
||||
}
|
||||
case *ast.ReturnStmt:
|
||||
switch n.Results[0].(type) {
|
||||
case *ast.CallExpr:
|
||||
return false
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
c.voidCalls[n] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *funcScope) stackSize() int64 {
|
||||
size := 0
|
||||
ast.Inspect(c.decl, func(n ast.Node) bool {
|
||||
switch n.(type) {
|
||||
switch n := n.(type) {
|
||||
case *ast.AssignStmt, *ast.ReturnStmt, *ast.IfStmt:
|
||||
size++
|
||||
// This handles the inline GenDecl like "var x = 2"
|
||||
case *ast.GenDecl:
|
||||
switch t := n.Specs[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
if len(t.Values) > 0 {
|
||||
size++
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
@ -53,11 +93,11 @@ func (c *funcScope) stackSize() int64 {
|
|||
if c.decl.Recv != nil {
|
||||
numArgs += len(c.decl.Recv.List)
|
||||
}
|
||||
return int64(size + numArgs)
|
||||
return int64(size + numArgs + len(c.voidCalls))
|
||||
}
|
||||
|
||||
func (c *funcScope) newStruct() *structScope {
|
||||
strct := newStructScope()
|
||||
func (c *funcScope) newStruct(t *types.Struct) *structScope {
|
||||
strct := newStructScope(t)
|
||||
c.structs[len(c.scope)] = strct
|
||||
return strct
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
"log"
|
||||
)
|
||||
|
@ -9,16 +9,61 @@ import (
|
|||
// A structScope holds the positions for it's fields. Struct fields have different
|
||||
// positions then local variables in any scope.
|
||||
type structScope struct {
|
||||
// identifier of the initialized struct in the program.
|
||||
name string
|
||||
// A pointer to the underlying type.
|
||||
t *types.Struct
|
||||
|
||||
// a mapping of field identifier and its position.
|
||||
// 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
|
||||
}
|
||||
|
||||
func newStructScope() *structScope {
|
||||
return &structScope{
|
||||
fields: map[string]int{},
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,20 +76,13 @@ func (s *structScope) newField(name string) int {
|
|||
func (s *structScope) loadField(name string) int {
|
||||
i, ok := s.fields[name]
|
||||
if !ok {
|
||||
log.Fatalf("could not resolve field name %s for struct %s", name, s.name)
|
||||
log.Fatalf("could not resolve field %s for struct %v", name, s)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (s *structScope) initializeFields(ident *ast.Ident, tInfo *types.Info) {
|
||||
def, ok := tInfo.Defs[ident]
|
||||
if !ok {
|
||||
log.Fatalf("could not initialize fields of %s: definitions not found in typeinfo", ident.Name)
|
||||
}
|
||||
t, ok := def.Type().Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
log.Fatalf("%s is not of type struct", ident.Name)
|
||||
}
|
||||
func (s *structScope) initialize(t *types.Struct) {
|
||||
s.t = t
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
s.newField(t.Field(i).Name())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
var arrayTestCases = []testCase{
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
var assignTestCases = []testCase{
|
||||
{
|
||||
|
@ -28,4 +28,88 @@ var assignTestCases = []testCase{
|
|||
`,
|
||||
"53c56b546c766b00527ac4586c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"add assign",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 4
|
||||
x += 8
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"53c56b546c766b00527ac46c766b00c358936c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"sub assign",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 4
|
||||
x -= 2
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"53c56b546c766b00527ac46c766b00c352946c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"mul assign",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 4
|
||||
x *= 2
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"53c56b546c766b00527ac46c766b00c352956c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"div assign",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 4
|
||||
x /= 2
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"53c56b546c766b00527ac46c766b00c352966c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"add assign binary expr",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 4
|
||||
x += 6 + 2
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"53c56b546c766b00527ac46c766b00c358936c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"add assign binary expr ident",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 4
|
||||
y := 5
|
||||
x += 6 + y
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"54c56b546c766b00527ac4556c766b51527ac46c766b00c3566c766b51c393936c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"decl assign",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
var x int = 4
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"52c56b546c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
}
|
||||
|
|
8
pkg/vm/compiler/tests/bar/bar.go
Normal file
8
pkg/vm/compiler/tests/bar/bar.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package bar
|
||||
|
||||
type Bar struct {
|
||||
X int
|
||||
Y int
|
||||
Z string
|
||||
B bool
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
var binaryExprTestCases = []testCase{
|
||||
{
|
||||
|
@ -69,4 +69,46 @@ var binaryExprTestCases = []testCase{
|
|||
`,
|
||||
"54c56b546c766b00527ac4586c766b51527ac46c766b00c35293529358946c766b52527ac46203006c766b51c36c766b52c395616c7566",
|
||||
},
|
||||
{
|
||||
"compare equal strings",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
str := "a string"
|
||||
if str == "another string" {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"54c56b086120737472696e676c766b00527ac46c766b00c30e616e6f7468657220737472696e679c640b0062030051616c756662030000616c7566",
|
||||
},
|
||||
{
|
||||
"compare equal ints",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
if x == 10 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"54c56b5a6c766b00527ac46c766b00c35a9c640b0062030051616c756662030000616c7566",
|
||||
},
|
||||
{
|
||||
"compare not equal ints",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := 10
|
||||
if x != 10 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"54c56b5a6c766b00527ac46c766b00c35a9c640b0062030051616c756662030000616c7566",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
var boolTestCases = []testCase{
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -21,14 +21,25 @@ type testCase struct {
|
|||
|
||||
func TestAllCases(t *testing.T) {
|
||||
testCases := []testCase{}
|
||||
|
||||
// The Go language
|
||||
testCases = append(testCases, assignTestCases...)
|
||||
testCases = append(testCases, arrayTestCases...)
|
||||
testCases = append(testCases, binaryExprTestCases...)
|
||||
testCases = append(testCases, functionCallTestCases...)
|
||||
testCases = append(testCases, boolTestCases...)
|
||||
testCases = append(testCases, stringTestCases...)
|
||||
testCases = append(testCases, binaryExprTestCases...)
|
||||
testCases = append(testCases, structTestCases...)
|
||||
testCases = append(testCases, ifStatementTestCases...)
|
||||
testCases = append(testCases, customTypeTestCases...)
|
||||
|
||||
// TODO: issue #28
|
||||
// These tests are passing locally, but circleci is failing to resolve the dependency.
|
||||
// https://github.com/CityOfZion/neo-go/issues/28
|
||||
// testCases = append(testCases, importTestCases...)
|
||||
|
||||
// Blockchain specific
|
||||
// testCases = append(testCases, storageTestCases...)
|
||||
|
||||
for _, tc := range testCases {
|
||||
b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{})
|
||||
|
@ -54,16 +65,19 @@ func dumpOpCodeSideBySide(have, want []byte) {
|
|||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
||||
fmt.Fprintln(w, "INDEX\tHAVE OPCODE\tDESC\tWANT OPCODE\tDESC\tDIFF")
|
||||
|
||||
var b byte
|
||||
for i := 0; i < len(have); i++ {
|
||||
if len(want) <= i {
|
||||
break
|
||||
b = 0x00
|
||||
} else {
|
||||
b = want[i]
|
||||
}
|
||||
diff := ""
|
||||
if have[i] != want[i] {
|
||||
if have[i] != b {
|
||||
diff = "<<"
|
||||
}
|
||||
fmt.Fprintf(w, "%d\t0x%2x\t%s\t0x%2x\t%s\t%s\n",
|
||||
i, have[i], vm.Opcode(have[i]), want[i], vm.Opcode(want[i]), diff)
|
||||
i, have[i], vm.Opcode(have[i]), b, vm.Opcode(b), diff)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
|
25
pkg/vm/compiler/tests/custom_type_test.go
Normal file
25
pkg/vm/compiler/tests/custom_type_test.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package compiler
|
||||
|
||||
var customTypeTestCases = []testCase{
|
||||
{
|
||||
"test custom type",
|
||||
`
|
||||
package foo
|
||||
|
||||
type bar int
|
||||
type specialString string
|
||||
|
||||
func Main() specialString {
|
||||
var x bar
|
||||
var str specialString
|
||||
x = 10
|
||||
str = "some short string"
|
||||
if x == 10 {
|
||||
return str
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
`,
|
||||
"55c56b5a6c766b00527ac411736f6d652073686f727420737472696e676c766b51527ac46c766b00c35a9c640f006203006c766b51c3616c7566620300046e6f6e65616c7566",
|
||||
},
|
||||
}
|
6
pkg/vm/compiler/tests/foo/foo.go
Normal file
6
pkg/vm/compiler/tests/foo/foo.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package foo
|
||||
|
||||
// NewBar return an integer \o/
|
||||
func NewBar() int {
|
||||
return 10
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
var functionCallTestCases = []testCase{
|
||||
{
|
||||
|
@ -18,6 +18,22 @@ var functionCallTestCases = []testCase{
|
|||
`,
|
||||
"53c56b5a6c766b00527ac461651c006c766b51527ac46203006c766b00c36c766b51c393616c756652c56b5a6c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"test function call with no assign",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
getSomeInteger()
|
||||
getSomeInteger()
|
||||
return 0
|
||||
}
|
||||
|
||||
func getSomeInteger() int {
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"53c56b616511007561650c007562030000616c756651c56b62030000616c7566",
|
||||
},
|
||||
{
|
||||
"multiple function calls",
|
||||
`
|
||||
|
@ -58,6 +74,21 @@ var functionCallTestCases = []testCase{
|
|||
`,
|
||||
"53c56b5a6c766b00527ac46c766b00c3616516006c766b51527ac46203006c766b51c3616c756653c56b6c766b00527ac4586c766b51527ac46203006c766b00c36c766b51c393616c7566",
|
||||
},
|
||||
{
|
||||
"function call with arguments of interface type",
|
||||
`
|
||||
package testcase
|
||||
func Main() interface{} {
|
||||
x := getSomeInteger(10)
|
||||
return x
|
||||
}
|
||||
|
||||
func getSomeInteger(x interface{}) interface{} {
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"52c56b5a616516006c766b00527ac46203006c766b00c3616c756652c56b6c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"function call with multiple arguments",
|
||||
`
|
||||
|
@ -73,4 +104,17 @@ var functionCallTestCases = []testCase{
|
|||
`,
|
||||
"52c56b52547c616516006c766b00527ac46203006c766b00c3616c756653c56b6c766b00527ac46c766b51527ac46203006c766b00c36c766b51c393616c7566",
|
||||
},
|
||||
{
|
||||
"test Main arguments",
|
||||
`
|
||||
package foo
|
||||
func Main(operation string, args []interface{}) int {
|
||||
if operation == "mintTokens" {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
`,
|
||||
"55c56b6c766b00527ac46c766b51527ac46c766b00c30a6d696e74546f6b656e739c640b0062030051616c756662030000616c7566",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
var ifStatementTestCases = []testCase{
|
||||
{
|
||||
|
|
34
pkg/vm/compiler/tests/import_test.go
Normal file
34
pkg/vm/compiler/tests/import_test.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package compiler
|
||||
|
||||
var importTestCases = []testCase{
|
||||
{
|
||||
"import function",
|
||||
`
|
||||
package somethingelse
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/compiler/tests/foo"
|
||||
|
||||
func Main() int {
|
||||
i := foo.NewBar()
|
||||
return i
|
||||
}
|
||||
`,
|
||||
"52c56b616516006c766b00527ac46203006c766b00c3616c756651c56b6203005a616c7566",
|
||||
},
|
||||
{
|
||||
"import test",
|
||||
`
|
||||
package somethingwedontcareabout
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/compiler/tests/bar"
|
||||
|
||||
func Main() int {
|
||||
b := bar.Bar{
|
||||
X: 4,
|
||||
}
|
||||
return b.Y
|
||||
}
|
||||
`,
|
||||
"52c56b6154c66b546c766b00527ac4006c766b51527ac4006c766b52527ac4006c766b53527ac46c6c766b00527ac46203006c766b00c351c3616c7566",
|
||||
},
|
||||
}
|
20
pkg/vm/compiler/tests/storage_test.go
Normal file
20
pkg/vm/compiler/tests/storage_test.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package compiler
|
||||
|
||||
var storageTestCases = []testCase{
|
||||
{
|
||||
"interop storage test",
|
||||
`
|
||||
package foo
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/storage"
|
||||
|
||||
func Main() int {
|
||||
ctx := storage.GetContext()
|
||||
storage.Put(ctx, "amount", 1000)
|
||||
amount := storage.GetInt(ctx, "amount")
|
||||
return amount
|
||||
}
|
||||
`,
|
||||
"54c56b6168164e656f2e53746f726167652e476574436f6e74657874616c766b00527ac46c766b00c306616d6f756e7402e803527261680f4e656f2e53746f726167652e507574616c766b00c306616d6f756e747c61680f4e656f2e53746f726167652e476574616c766b51527ac46203006c766b51c3616c756651c56b62030000616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c756653c56b6c766b00527ac46c766b51527ac462030000616c7566",
|
||||
},
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
var stringTestCases = []testCase{
|
||||
{
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
package compiler_test
|
||||
package compiler
|
||||
|
||||
var structTestCases = []testCase{
|
||||
{
|
||||
"struct field assign",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
t := token {
|
||||
x: 2,
|
||||
|
@ -19,6 +14,11 @@ var structTestCases = []testCase{
|
|||
age := t.x
|
||||
return age
|
||||
}
|
||||
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
`,
|
||||
"53c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac46c766b00c300c36c766b51527ac46203006c766b51c3616c7566",
|
||||
},
|
||||
|
@ -154,4 +154,50 @@ var structTestCases = []testCase{
|
|||
`,
|
||||
"53c56b6151c66b546c766b00527ac46c6c766b00527ac46c766b00c352545272616516006c766b51527ac46203006c766b51c3616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac46203006c766b00c300c36c766b51c3936c766b52c393616c7566",
|
||||
},
|
||||
{
|
||||
"initialize struct partially",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
z string
|
||||
b bool
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
t := token {
|
||||
x: 4,
|
||||
}
|
||||
return t.y
|
||||
}
|
||||
`,
|
||||
"52c56b6154c66b546c766b00527ac4006c766b51527ac4006c766b52527ac4006c766b53527ac46c6c766b00527ac46203006c766b00c351c3616c7566",
|
||||
},
|
||||
{
|
||||
"test return struct from func",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
z string
|
||||
b bool
|
||||
}
|
||||
|
||||
func newToken() token {
|
||||
return token{
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: "hello",
|
||||
b: false,
|
||||
}
|
||||
}
|
||||
|
||||
func Main() token {
|
||||
return newToken()
|
||||
}
|
||||
`,
|
||||
"51c56b62030061650700616c756651c56b6203006154c66b516c766b00527ac4526c766b51527ac40568656c6c6f6c766b52527ac4006c766b53527ac46c616c7566",
|
||||
},
|
||||
}
|
||||
|
|
14
pkg/vm/smartcontract/runtime/runtime.go
Normal file
14
pkg/vm/smartcontract/runtime/runtime.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package runtime
|
||||
|
||||
// TriggerType represents a byte.
|
||||
type TriggerType byte
|
||||
|
||||
// List of valid trigger types.
|
||||
const (
|
||||
Verification TriggerType = 0x00
|
||||
Application TriggerType = 0x10
|
||||
)
|
||||
|
||||
// GetTrigger return the current trigger type. The return in this function
|
||||
// doesn't really mather, this is just an interop placeholder.
|
||||
func GetTrigger() TriggerType { return 0x00 }
|
16
pkg/vm/smartcontract/storage/storage.go
Normal file
16
pkg/vm/smartcontract/storage/storage.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package storage
|
||||
|
||||
// GetContext ..
|
||||
func GetContext() int { return 0 }
|
||||
|
||||
// Put stores a value in to the storage.
|
||||
func Put(ctx interface{}, key interface{}, value interface{}) int { return 0 }
|
||||
|
||||
// GetInt returns the value as an integer.
|
||||
func GetInt(ctx interface{}, key interface{}) int { return 0 }
|
||||
|
||||
// GetString returns the value as an string.
|
||||
func GetString(ctx interface{}, key interface{}) string { return "" }
|
||||
|
||||
// Delete removes a stored key value pair.
|
||||
func Delete(ctx interface{}, key interface{}) int { return 0 }
|
12
pkg/vm/syscall.go
Normal file
12
pkg/vm/syscall.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package vm
|
||||
|
||||
// Syscalls is a mapping between the syscall function name
|
||||
// and the registerd VM interop API.
|
||||
var Syscalls = map[string]string{
|
||||
// Storage API
|
||||
"GetContext": "Neo.Storage.GetContext",
|
||||
"Put": "Neo.Storage.Put",
|
||||
"GetInt": "Neo.Storage.Get",
|
||||
"GetString": "Neo.Storage.Get",
|
||||
"Delete": "Neo.Storage.Delete",
|
||||
}
|
Loading…
Reference in a new issue