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:
Anthony De Meulemeester 2018-02-24 10:06:48 +01:00 committed by GitHub
parent bebdabab9f
commit 23cfebf621
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 798 additions and 117 deletions

View file

@ -1,9 +0,0 @@
language: go
sudo: false
go:
- 1.x
- tip
install:
- go build ./{cmd,pkg}/...
script:
- go vet ./{cmd,pkg}/...

View file

@ -1 +1 @@
0.12.0
0.13.0

View file

@ -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
```

View file

@ -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:
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:
// 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)
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
// Fields initialized by the program.
for _, field := range lit.Elts {
f := field.(*ast.KeyValueExpr)
// Walk to resolve the expression of the value.
fieldName := f.Key.(*ast.Ident).Name
if sField.Name() == fieldName {
ast.Walk(c, f.Value)
l := strct.newField(f.Key.(*ast.Ident).Name)
c.emitStoreLocal(l)
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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -2,6 +2,7 @@ package compiler
import (
"go/ast"
"go/types"
"log"
)
@ -14,15 +15,23 @@ 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
}
@ -34,16 +43,47 @@ func newFuncScope(decl *ast.FuncDecl, label int) *funcScope {
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
}

View file

@ -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{
// 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())
}

View file

@ -1,4 +1,4 @@
package compiler_test
package compiler
var arrayTestCases = []testCase{
{

View file

@ -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",
},
}

View file

@ -0,0 +1,8 @@
package bar
type Bar struct {
X int
Y int
Z string
B bool
}

View file

@ -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",
},
}

View file

@ -1,4 +1,4 @@
package compiler_test
package compiler
var boolTestCases = []testCase{
{

View file

@ -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()
}

View 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",
},
}

View file

@ -0,0 +1,6 @@
package foo
// NewBar return an integer \o/
func NewBar() int {
return 10
}

View file

@ -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",
},
}

View file

@ -1,4 +1,4 @@
package compiler_test
package compiler
var ifStatementTestCases = []testCase{
{

View 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",
},
}

View 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",
},
}

View file

@ -1,4 +1,4 @@
package compiler_test
package compiler
var stringTestCases = []testCase{
{

View file

@ -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",
},
}

View 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 }

View 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
View 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",
}