Refactor of imports + lots of sweet stuff (#30)

* implemented global variables.

* refactored imports + lots and lots of other sweet stuff + fix #28.

* Implemented the VM interop runtime API (GetTrigger, CheckWitness, ...)
This commit is contained in:
Anthony De Meulemeester 2018-02-25 13:26:56 +01:00 committed by GitHub
parent 63bc244163
commit de3395fb51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 344 additions and 158 deletions

View file

@ -1 +1 @@
0.15.0 0.16.0

View file

@ -0,0 +1,36 @@
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 }
// CheckWitness verifies if the invoker is the owner of the contract.
func CheckWitness(hash T) T { return 0 }
// GetCurrentBlock returns the current block.
func GetCurrentBlock() core.Block { return core.Block{} }
// GetTime returns the timestamp of the most recent block.
func GetTime() int { return 0 }
// Notify an event to the VM.
func Notify(arg T) T { 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
}
// Application returns the application trigger type.
func Application() byte {
return 0x10
}

View file

@ -1,7 +1,7 @@
package storage package storage
// GetContext .. // GetContext ..
func GetContext() int { return 0 } func GetContext() interface{} { return 0 }
// Put stores a value in to the storage. // Put stores a value in to the storage.
func Put(ctx interface{}, key interface{}, value interface{}) int { return 0 } func Put(ctx interface{}, key interface{}, value interface{}) int { return 0 }

View file

@ -15,7 +15,8 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
### Go internals ### Go internals
- type checker - type checker
- multiple assigns - multiple assigns
- types int, string and booleans - global variables
- types int, string, byte and booleans
- struct types + method receives - struct types + method receives
- functions - functions
- composite literals `[]int, []string` - composite literals `[]int, []string`
@ -26,12 +27,13 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
### VM API (interop layer) ### VM API (interop layer)
- storage - storage
- runtime
## Not yet implemented ## Not yet implemented
- for loops - for loops
- ranges - ranges
- builtins (append, len, ..) - builtins (append, len, ..)
- large part of the interop layer (VM API) - some parts of the interop layer (VM API)
## Not supported ## Not supported
Due to the limitations of the NEO virtual machine, features listed below will not be supported. Due to the limitations of the NEO virtual machine, features listed below will not be supported.

View file

@ -0,0 +1,71 @@
package compiler
import (
"go/ast"
"go/constant"
"go/types"
"log"
"golang.org/x/tools/go/loader"
)
// countGlobals counts the global variables in the program to add
// them with the stacksize of the function.
func countGlobals(f *ast.File) (i int64) {
ast.Inspect(f, func(node ast.Node) bool {
switch node.(type) {
// Skip all functio declarations.
case *ast.FuncDecl:
return false
// After skipping all funcDecls we are sure that each value spec
// is a global declared variable or constant.
case *ast.ValueSpec:
i++
}
return true
})
return
}
// isIdentBool looks if the given ident is a boolean.
func isIdentBool(ident *ast.Ident) bool {
return ident.Name == "true" || ident.Name == "false"
}
// makeBoolFromIdent creates a bool type from an *ast.Ident.
func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) types.TypeAndValue {
var b bool
if ident.Name == "true" {
b = true
} else if ident.Name == "false" {
b = false
} else {
log.Fatalf("givent identifier cannot be converted to a boolean => %s", ident.Name)
}
return types.TypeAndValue{
Type: tinfo.ObjectOf(ident).Type(),
Value: constant.MakeBool(b),
}
}
// resolveEntryPoint returns the function declaration of the entrypoint and the corresponding file.
func resolveEntryPoint(entry string, pkg *loader.PackageInfo) (*ast.FuncDecl, *ast.File) {
var (
main *ast.FuncDecl
file *ast.File
)
for _, f := range pkg.Files {
ast.Inspect(f, func(n ast.Node) bool {
switch t := n.(type) {
case *ast.FuncDecl:
if t.Name.Name == entry {
main = t
file = f
return false
}
}
return true
})
}
return main, file
}

View file

@ -15,16 +15,20 @@ import (
const mainIdent = "Main" const mainIdent = "Main"
type codegen struct { type codegen struct {
// Information about the program with all its dependencies.
buildInfo *buildInfo
// prog holds the output buffer
prog *bytes.Buffer prog *bytes.Buffer
// Type information // Type information
typeInfo *types.Info typeInfo *types.Info
// a mapping of func identifiers with its scope. // A mapping of func identifiers with their scope.
funcs map[string]*funcScope funcs map[string]*funcScope
// current function being generated // Current funcScope being converted.
fctx *funcScope scope *funcScope
// Label table for recording jump destinations. // Label table for recording jump destinations.
l []int l []int
@ -50,15 +54,21 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) {
switch typ := t.Type.Underlying().(type) { switch typ := t.Type.Underlying().(type) {
case *types.Basic: case *types.Basic:
switch typ.Kind() { switch typ.Kind() {
case types.Int: case types.Int, types.UntypedInt:
val, _ := constant.Int64Val(t.Value) val, _ := constant.Int64Val(t.Value)
emitInt(c.prog, val) emitInt(c.prog, val)
case types.String: case types.String, types.UntypedString:
val := constant.StringVal(t.Value) val := constant.StringVal(t.Value)
emitString(c.prog, val) emitString(c.prog, val)
case types.Bool, types.UntypedBool: case types.Bool, types.UntypedBool:
val := constant.BoolVal(t.Value) val := constant.BoolVal(t.Value)
emitBool(c.prog, val) emitBool(c.prog, val)
case types.Byte:
val, _ := constant.Int64Val(t.Value)
b := byte(val)
emitBytes(c.prog, []byte{b})
default:
log.Fatalf("compiler don't know how to convert this basic type: %v", t)
} }
default: default:
log.Fatalf("compiler don't know how to convert this constant: %v", t) log.Fatalf("compiler don't know how to convert this constant: %v", t)
@ -66,7 +76,7 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) {
} }
func (c *codegen) emitLoadLocal(name string) { func (c *codegen) emitLoadLocal(name string) {
pos := c.fctx.loadLocal(name) pos := c.scope.loadLocal(name)
if pos < 0 { if pos < 0 {
log.Fatalf("cannot load local variable with position: %d", pos) log.Fatalf("cannot load local variable with position: %d", pos)
} }
@ -80,7 +90,7 @@ func (c *codegen) emitLoadLocal(name string) {
} }
func (c *codegen) emitLoadStructField(sName, fName string) { func (c *codegen) emitLoadStructField(sName, fName string) {
strct := c.fctx.loadStruct(sName) strct := c.scope.loadStruct(sName)
pos := strct.loadField(fName) pos := strct.loadField(fName)
emitInt(c.prog, int64(pos)) emitInt(c.prog, int64(pos))
emitOpcode(c.prog, vm.Opickitem) emitOpcode(c.prog, vm.Opickitem)
@ -102,14 +112,42 @@ func (c *codegen) emitStoreLocal(pos int) {
} }
func (c *codegen) emitStoreStructField(sName, fName string) { func (c *codegen) emitStoreStructField(sName, fName string) {
strct := c.fctx.loadStruct(sName) strct := c.scope.loadStruct(sName)
pos := strct.loadField(fName) pos := strct.loadField(fName)
emitInt(c.prog, int64(pos)) emitInt(c.prog, int64(pos))
emitOpcode(c.prog, vm.Orot) emitOpcode(c.prog, vm.Orot)
emitOpcode(c.prog, vm.Osetitem) emitOpcode(c.prog, vm.Osetitem)
} }
func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) { func (c *codegen) emitSyscallReturn() {
emitOpcode(c.prog, vm.Ojmp)
emitOpcode(c.prog, vm.Opcode(0x03))
emitOpcode(c.prog, vm.Opush0)
emitInt(c.prog, int64(0))
emitOpcode(c.prog, vm.Onop)
emitOpcode(c.prog, vm.Ofromaltstack)
emitOpcode(c.prog, vm.Odrop)
emitOpcode(c.prog, vm.Oret)
}
// convertGlobals will traverse the AST and only convert global declarations.
// If we call this in convertFuncDecl then it will load all global variables
// into the scope of the function.
func (c *codegen) convertGlobals(f *ast.File) {
ast.Inspect(f, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.FuncDecl:
return false
case *ast.GenDecl:
ast.Walk(c, n)
}
return true
})
}
func (c *codegen) convertFuncDecl(file *ast.File, decl *ast.FuncDecl) {
var ( var (
f *funcScope f *funcScope
ok bool ok bool
@ -122,10 +160,12 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
f = c.newFunc(decl) f = c.newFunc(decl)
} }
c.fctx = f c.scope = f
ast.Inspect(decl, c.fctx.analyzeVoidCalls) ast.Inspect(decl, c.scope.analyzeVoidCalls)
emitInt(c.prog, f.stackSize()) // All globals copied into the scope of the function need to be added
// to the stack size of the function.
emitInt(c.prog, f.stackSize()+countGlobals(file))
emitOpcode(c.prog, vm.Onewarray) emitOpcode(c.prog, vm.Onewarray)
emitOpcode(c.prog, vm.Otoaltstack) emitOpcode(c.prog, vm.Otoaltstack)
@ -142,8 +182,8 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
if !ok { if !ok {
log.Fatal("method receiver is not a struct type") log.Fatal("method receiver is not a struct type")
} }
c.fctx.newStruct(t) c.scope.newStruct(t)
l := c.fctx.newLocal(ident.Name) l := c.scope.newLocal(ident.Name)
c.emitStoreLocal(l) c.emitStoreLocal(l)
} }
} }
@ -151,28 +191,41 @@ func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
// Load the arguments in scope. // Load the arguments in scope.
for _, arg := range decl.Type.Params.List { for _, arg := range decl.Type.Params.List {
name := arg.Names[0].Name // for now. name := arg.Names[0].Name // for now.
l := c.fctx.newLocal(name) l := c.scope.newLocal(name)
c.emitStoreLocal(l) c.emitStoreLocal(l)
} }
// If this function is a syscall we will manipulate the return value to 0.
// All the syscalls are just signatures functions and bring no real return value.
// The return values you will find in the smartcontract package is just for
// satisfying the typechecker and the user experience.
if isSyscall(f.name) {
c.emitSyscallReturn()
} else {
// After loading the arguments we can convert the globals into the scope of the function.
c.convertGlobals(file)
ast.Walk(c, decl.Body) ast.Walk(c, decl.Body)
} }
}
func (c *codegen) Visit(node ast.Node) ast.Visitor { func (c *codegen) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) { switch n := node.(type) {
// General declarations. // General declarations.
// With value: var x int = 2 // var (
// Without value: var x int // x = 2
// )
case *ast.GenDecl: case *ast.GenDecl:
switch t := n.Specs[0].(type) { for _, spec := range n.Specs {
switch t := spec.(type) {
case *ast.ValueSpec: case *ast.ValueSpec:
if len(t.Values) > 0 { for i, val := range t.Values {
ast.Walk(c, t.Values[0]) ast.Walk(c, val)
l := c.fctx.newLocal(t.Names[0].Name) l := c.scope.newLocal(t.Names[i].Name)
c.emitStoreLocal(l) c.emitStoreLocal(l)
} }
} }
}
return nil return nil
case *ast.AssignStmt: case *ast.AssignStmt:
@ -184,11 +237,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.emitLoadLocal(t.Name) c.emitLoadLocal(t.Name)
ast.Walk(c, n.Rhs[0]) ast.Walk(c, n.Rhs[0])
c.convertToken(n.Tok) c.convertToken(n.Tok)
l := c.fctx.loadLocal(t.Name) l := c.scope.loadLocal(t.Name)
c.emitStoreLocal(l) c.emitStoreLocal(l)
default: default:
ast.Walk(c, n.Rhs[0]) ast.Walk(c, n.Rhs[0])
l := c.fctx.loadLocal(t.Name) l := c.scope.loadLocal(t.Name)
c.emitStoreLocal(l) c.emitStoreLocal(l)
} }
@ -246,7 +299,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
return nil return nil
case *ast.BasicLit: case *ast.BasicLit:
c.emitLoadConst(c.getTypeInfo(n)) c.emitLoadConst(c.typeInfo.Types[n])
return nil return nil
case *ast.Ident: case *ast.Ident:
@ -268,7 +321,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
default: default:
ln := len(n.Elts) ln := len(n.Elts)
for i := ln - 1; i >= 0; i-- { for i := ln - 1; i >= 0; i-- {
c.emitLoadConst(c.getTypeInfo(n.Elts[i])) c.emitLoadConst(c.typeInfo.Types[n.Elts[i]])
} }
emitInt(c.prog, int64(ln)) emitInt(c.prog, int64(ln))
emitOpcode(c.prog, vm.Opack) emitOpcode(c.prog, vm.Opack)
@ -300,9 +353,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// The AST package will try to resolve all basic literals for us. // The AST package will try to resolve all basic literals for us.
// If the typeinfo.Value is not nil we know that the expr is resolved // If the typeinfo.Value is not nil we know that the expr is resolved
// and needs no further action. e.g. x := 2 + 2 + 2 will be resolved to 6. // and needs no further action. e.g. x := 2 + 2 + 2 will be resolved to 6.
if tinfo := c.getTypeInfo(n); tinfo.Value != nil { // NOTE: Constants will also be automagically resolved be the AST parser.
// example:
// const x = 10
// x + 2 will results into 12
if tinfo := c.typeInfo.Types[n]; tinfo.Value != nil {
c.emitLoadConst(tinfo) c.emitLoadConst(tinfo)
return c return nil
} }
ast.Walk(c, n.X) ast.Walk(c, n.X)
@ -364,7 +421,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// If we are not assigning this function to a variable we need to drop // 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/. // 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) { if _, ok := c.scope.voidCalls[n]; ok && !isNoRetSyscall(f.name) {
emitOpcode(c.prog, vm.Odrop) emitOpcode(c.prog, vm.Odrop)
} }
return nil return nil
@ -372,8 +429,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.SelectorExpr: case *ast.SelectorExpr:
switch t := n.X.(type) { switch t := n.X.(type) {
case *ast.Ident: case *ast.Ident:
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
switch typ.(type) {
case *types.Struct:
c.emitLoadLocal(t.Name) // load the struct c.emitLoadLocal(t.Name) // load the struct
c.emitLoadStructField(t.Name, n.Sel.Name) // load the field c.emitLoadStructField(t.Name, n.Sel.Name) // load the field
default:
log.Fatal("non struct import selections not yet implemented, please use functions instead")
}
default: default:
log.Fatal("nested selectors not supported yet") log.Fatal("nested selectors not supported yet")
} }
@ -398,7 +461,7 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) {
if !ok { if !ok {
log.Fatalf("the given literal is not of type struct: %v", lit) log.Fatalf("the given literal is not of type struct: %v", lit)
} }
strct := c.fctx.newStruct(t) strct := c.scope.newStruct(t)
emitOpcode(c.prog, vm.Onop) emitOpcode(c.prog, vm.Onop)
emitInt(c.prog, int64(strct.t.NumFields())) emitInt(c.prog, int64(strct.t.NumFields()))
@ -472,84 +535,45 @@ func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
return 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]
}
func makeBoolFromIdent(ident *ast.Ident, tinfo *types.Info) types.TypeAndValue {
var b bool
if ident.Name == "true" {
b = true
} else if ident.Name == "false" {
b = false
} else {
log.Fatalf("givent identifier cannot be converted to a boolean => %s", ident.Name)
}
return types.TypeAndValue{
Type: tinfo.ObjectOf(ident).Type(),
Value: constant.MakeBool(b),
}
}
func isIdentBool(ident *ast.Ident) bool {
return ident.Name == "true" || ident.Name == "false"
}
// CodeGen is the function that compiles the program to bytecode. // CodeGen is the function that compiles the program to bytecode.
func CodeGen(f *ast.File, tInfo *types.Info, imports map[string]*archive) (*bytes.Buffer, error) { func CodeGen(info *buildInfo) (*bytes.Buffer, error) {
pkg := info.program.Package(info.initialPackage)
c := &codegen{ c := &codegen{
buildInfo: info,
prog: new(bytes.Buffer), prog: new(bytes.Buffer),
l: []int{}, l: []int{},
funcs: map[string]*funcScope{}, funcs: map[string]*funcScope{},
typeInfo: tInfo, typeInfo: &pkg.Info,
} }
var main *ast.FuncDecl // Resolve the entrypoint of the program
ast.Inspect(f, func(n ast.Node) bool { main, mainFile := resolveEntryPoint(mainIdent, pkg)
switch t := n.(type) {
case *ast.FuncDecl:
if t.Name.Name == mainIdent {
main = t
return false
}
}
return true
})
if main == nil { if main == nil {
log.Fatal("could not find func main. did you forgot to declare it?") log.Fatal("could not find func main. did you forgot to declare it?")
} }
// Bring all imported functions into scope // Bring all imported functions into scope
for _, arch := range imports { for _, pkg := range info.program.AllPackages {
c.resolveFuncDecls(arch.f) for _, f := range pkg.Files {
c.resolveFuncDecls(f)
}
} }
c.resolveFuncDecls(f) // convert the entry point first
c.convertFuncDecl(main) c.convertFuncDecl(mainFile, main)
// 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 { for _, decl := range f.Decls {
switch n := decl.(type) { switch n := decl.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
if n.Name.Name != mainIdent { if n.Name.Name != mainIdent {
c.convertFuncDecl(n) c.convertFuncDecl(f, n)
} }
} }
} }
// 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)
}
}
} }
} }

View file

@ -19,6 +19,7 @@ import (
"text/tabwriter" "text/tabwriter"
"github.com/CityOfZion/neo-go/pkg/vm" "github.com/CityOfZion/neo-go/pkg/vm"
"golang.org/x/tools/go/loader"
) )
const fileExt = "avm" const fileExt = "avm"
@ -35,36 +36,31 @@ type Options struct {
Debug bool Debug bool
} }
type buildInfo struct {
initialPackage string
program *loader.Program
}
// Compile compiles a Go program into bytecode that can run on the NEO virtual machine. // Compile compiles a Go program into bytecode that can run on the NEO virtual machine.
func Compile(input io.Reader, o *Options) ([]byte, error) { func Compile(r io.Reader, o *Options) ([]byte, error) {
fset := token.NewFileSet() conf := loader.Config{ParserMode: parser.ParseComments}
f, err := parser.ParseFile(fset, "", input, 0) f, err := conf.ParseFile("", r)
if err != nil {
return nil, err
}
conf.CreateFromFiles("", f)
prog, err := conf.Load()
if err != nil { if err != nil {
return nil, err return nil, err
} }
conf := types.Config{Importer: importer.Default()} ctx := &buildInfo{
typeInfo := &types.Info{ initialPackage: f.Name.Name,
Types: make(map[ast.Expr]types.TypeAndValue), program: prog,
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 buf, err := CodeGen(ctx)
_, err = conf.Check("", fset, []*ast.File{f}, typeInfo)
if err != nil {
return nil, err
}
imports, err := resolveImports(f)
if err != nil {
return nil, err
}
buf, err := CodeGen(f, typeInfo, imports)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -6,20 +6,20 @@ import (
"log" "log"
) )
// A funcScope represents a scope within the function context. // A funcScope represents the scope within the function context.
// It holds al the local variables along with the initialized struct positions. // It holds al the local variables along with the initialized struct positions.
type funcScope struct { type funcScope struct {
// function identifier // identifier of the function.
name string name string
// The declaration of the function in the AST // The declaration of the function in the AST. Nil if this scope is not a function.
decl *ast.FuncDecl decl *ast.FuncDecl
// Program label of the function // Program label of the scope
label int label int
// Local scope of the function // Local variables
scope map[string]int locals map[string]int
// A mapping of structs positions with their scope // A mapping of structs positions with their scope
structs map[int]*structScope structs map[int]*structScope
@ -41,7 +41,7 @@ func newFuncScope(decl *ast.FuncDecl, label int) *funcScope {
name: decl.Name.Name, name: decl.Name.Name,
decl: decl, decl: decl,
label: label, label: label,
scope: map[string]int{}, locals: map[string]int{},
structs: map[int]*structScope{}, structs: map[int]*structScope{},
voidCalls: map[*ast.CallExpr]bool{}, voidCalls: map[*ast.CallExpr]bool{},
i: -1, i: -1,
@ -64,8 +64,11 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
case *ast.CallExpr: case *ast.CallExpr:
return false return false
} }
case *ast.BinaryExpr:
return false
case *ast.CallExpr: case *ast.CallExpr:
c.voidCalls[n] = true c.voidCalls[n] = true
return false
} }
return true return true
} }
@ -98,7 +101,7 @@ func (c *funcScope) stackSize() int64 {
func (c *funcScope) newStruct(t *types.Struct) *structScope { func (c *funcScope) newStruct(t *types.Struct) *structScope {
strct := newStructScope(t) strct := newStructScope(t)
c.structs[len(c.scope)] = strct c.structs[len(c.locals)] = strct
return strct return strct
} }
@ -114,13 +117,13 @@ func (c *funcScope) loadStruct(name string) *structScope {
// newLocal creates a new local variable into the scope of the function. // newLocal creates a new local variable into the scope of the function.
func (c *funcScope) newLocal(name string) int { func (c *funcScope) newLocal(name string) int {
c.i++ c.i++
c.scope[name] = c.i c.locals[name] = c.i
return c.i return c.i
} }
// loadLocal loads the position of a local variable inside the scope of the function. // loadLocal loads the position of a local variable inside the scope of the function.
func (c *funcScope) loadLocal(name string) int { func (c *funcScope) loadLocal(name string) int {
i, ok := c.scope[name] i, ok := c.locals[name]
if !ok { if !ok {
// should emit a compiler warning. // should emit a compiler warning.
return c.newLocal(name) return c.newLocal(name)

View file

@ -32,14 +32,11 @@ func TestAllCases(t *testing.T) {
testCases = append(testCases, structTestCases...) testCases = append(testCases, structTestCases...)
testCases = append(testCases, ifStatementTestCases...) testCases = append(testCases, ifStatementTestCases...)
testCases = append(testCases, customTypeTestCases...) testCases = append(testCases, customTypeTestCases...)
testCases = append(testCases, constantTestCases...)
// TODO: issue #28 testCases = append(testCases, importTestCases...)
// 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 // Blockchain specific
// testCases = append(testCases, storageTestCases...) testCases = append(testCases, storageTestCases...)
for _, tc := range testCases { for _, tc := range testCases {
b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{}) b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{})
@ -54,8 +51,7 @@ func TestAllCases(t *testing.T) {
if bytes.Compare(b, expectedResult) != 0 { if bytes.Compare(b, expectedResult) != 0 {
t.Log(hex.EncodeToString(b)) t.Log(hex.EncodeToString(b))
want, _ := hex.DecodeString(tc.result) dumpOpCodeSideBySide(b, expectedResult)
dumpOpCodeSideBySide(b, want)
t.Fatalf("compiling %s failed", tc.name) t.Fatalf("compiling %s failed", tc.name)
} }
} }

View file

@ -0,0 +1,64 @@
package compiler
var constantTestCases = []testCase{
{
"basic constant",
`
package foo
const x = 10
func Main() int {
return x + 2
}
`,
// This ouput wil not be the same als the boa compiler.
// The go compiler will automatically resolve binary expressions
// involving constants.
// x + 2 in this case will be resolved to 12.
"52c56b5a6c766b00527ac46203005c616c7566",
},
{
"shorthand multi const",
`
package foo
const (
z = 3
y = 2
x = 1
)
// should load al 3 constants in the Main.
func Main() int {
return 0
}
`,
"54c56b536c766b00527ac4526c766b51527ac4516c766b52527ac462030000616c7566",
},
{
"globals with function arguments",
`
package foobar
const (
bar = "FOO"
foo = "BAR"
)
func something(x int) string {
if x > 10 {
return bar
}
return foo
}
func Main() string {
trigger := 100
x := something(trigger)
return x
}
`,
"55c56b03464f4f6c766b00527ac4034241526c766b51527ac401646c766b52527ac46c766b52c3616516006c766b53527ac46203006c766b53c3616c756656c56b6c766b00527ac403464f4f6c766b51527ac4034241526c766b52527ac46c766b00c35aa0640f006203006c766b51c3616c75666203006c766b52c3616c7566",
},
}

View file

@ -6,7 +6,7 @@ var storageTestCases = []testCase{
` `
package foo package foo
import "github.com/CityOfZion/neo-go/pkg/vm/smartcontract/storage" import "github.com/CityOfZion/neo-go/pkg/smartcontract/storage"
func Main() int { func Main() int {
ctx := storage.GetContext() ctx := storage.GetContext()

View file

@ -1,14 +0,0 @@
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

@ -9,4 +9,12 @@ var Syscalls = map[string]string{
"GetInt": "Neo.Storage.Get", "GetInt": "Neo.Storage.Get",
"GetString": "Neo.Storage.Get", "GetString": "Neo.Storage.Get",
"Delete": "Neo.Storage.Delete", "Delete": "Neo.Storage.Delete",
// Runtime
"GetTrigger": "Neo.Runtime.GetTrigger",
"CheckWitness": "Neo.Runtime.CheckWitness",
"GetCurrentBlock": "Neo.Runtime.GetCurrentBlock",
"GetTime": "Neo.Runtime.GetTime",
"Notify": "Neo.runtime.Notify",
"Log": "Neo.Runtime.Log",
} }