Update compiler (#22)
* refactor to use ast.Walk for recursive converting * added lots of test cases * added a new way to handle jump labels * function calls with multiple arguments * binary expression (LOR LAND) * struct types + method receives * cleaner opcode dumps, side by side diff for debugging test cases
This commit is contained in:
parent
b257a06f3e
commit
8fe079ec8e
19 changed files with 1298 additions and 1064 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.10.0
|
||||
0.11.0
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
package smartcontract
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/vm/compiler"
|
||||
"github.com/urfave/cli"
|
||||
|
@ -43,39 +38,19 @@ func contractCompile(ctx *cli.Context) error {
|
|||
return errors.New("not enough arguments")
|
||||
}
|
||||
|
||||
o := &compiler.Options{
|
||||
Outfile: ctx.String("out"),
|
||||
Debug: true,
|
||||
}
|
||||
|
||||
src := ctx.Args()[0]
|
||||
c := compiler.New()
|
||||
if err := c.CompileSource(src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := strings.Split(src, ".")[0]
|
||||
filename = filename + ".avm"
|
||||
|
||||
out := ctx.String("out")
|
||||
if len(out) > 0 {
|
||||
filename = out
|
||||
}
|
||||
|
||||
f, err := os.Create(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hx := hex.EncodeToString(c.Buffer().Bytes())
|
||||
fmt.Println(hx)
|
||||
|
||||
_, err = io.Copy(f, c.Buffer())
|
||||
return err
|
||||
return compiler.CompileAndSave(src, o)
|
||||
}
|
||||
|
||||
func contractDumpOpcode(ctx *cli.Context) error {
|
||||
src := ctx.Args()[0]
|
||||
|
||||
c := compiler.New()
|
||||
if err := c.CompileSource(src); err != nil {
|
||||
return err
|
||||
if len(ctx.Args()) == 0 {
|
||||
return errors.New("not enough arguments")
|
||||
}
|
||||
c.DumpOpcode()
|
||||
return nil
|
||||
src := ctx.Args()[0]
|
||||
return compiler.DumpOpcode(src)
|
||||
}
|
||||
|
|
35
pkg/vm/compiler/README.md
Normal file
35
pkg/vm/compiler/README.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# NEO-GO Compiler
|
||||
|
||||
The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand.
|
||||
|
||||
> The neo-go compiler is under very active development and will be updated on a weekly basis.
|
||||
|
||||
## Currently supported
|
||||
- type checker
|
||||
- multiple assigns
|
||||
- types int, string and bool
|
||||
- struct types + method receives
|
||||
- functions
|
||||
- composite literals `[]int, []string`
|
||||
- basic if statements
|
||||
- binary expressions.
|
||||
- return statements
|
||||
|
||||
## Not yet implemented
|
||||
- for loops
|
||||
- ranges
|
||||
- builtins (append, len, ..)
|
||||
- blockchain helpers (sha256, storage, ..)
|
||||
- import packages
|
||||
|
||||
## Not supported
|
||||
Due to the limitations of the NEO virtual machine, features listed below will not be supported.
|
||||
- channels
|
||||
- goroutines
|
||||
- multiple returns
|
||||
|
||||
## How to report 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
|
472
pkg/vm/compiler/codegen.go
Normal file
472
pkg/vm/compiler/codegen.go
Normal file
|
@ -0,0 +1,472 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
const mainIdent = "Main"
|
||||
|
||||
type codegen struct {
|
||||
prog *bytes.Buffer
|
||||
|
||||
// Type information
|
||||
typeInfo *types.Info
|
||||
|
||||
// a mapping of func identifiers with its scope.
|
||||
funcs map[string]*funcScope
|
||||
|
||||
// current function being generated
|
||||
fctx *funcScope
|
||||
|
||||
// Label table for recording jump destinations.
|
||||
l []int
|
||||
}
|
||||
|
||||
// newLabel creates a new label to jump to
|
||||
func (c *codegen) newLabel() (l int) {
|
||||
l = len(c.l)
|
||||
c.l = append(c.l, -1)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *codegen) setLabel(l int) {
|
||||
c.l[l] = c.pc() + 1
|
||||
}
|
||||
|
||||
// pc return the program offset off the last instruction.
|
||||
func (c *codegen) pc() int {
|
||||
return c.prog.Len() - 1
|
||||
}
|
||||
|
||||
func (c *codegen) emitLoadConst(t types.TypeAndValue) {
|
||||
switch typ := t.Type.(type) {
|
||||
case *types.Basic:
|
||||
switch typ.Kind() {
|
||||
case types.Int:
|
||||
val, _ := constant.Int64Val(t.Value)
|
||||
emitInt(c.prog, val)
|
||||
case types.String:
|
||||
val := constant.StringVal(t.Value)
|
||||
emitString(c.prog, val)
|
||||
case types.Bool, types.UntypedBool:
|
||||
val := constant.BoolVal(t.Value)
|
||||
emitBool(c.prog, val)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("compiler don't know how to convert this constant: %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *codegen) emitLoadLocal(name string) {
|
||||
pos := c.fctx.loadLocal(name)
|
||||
if pos < 0 {
|
||||
log.Fatalf("cannot load local variable with position: %d", pos)
|
||||
}
|
||||
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
emitOpcode(c.prog, vm.Odup)
|
||||
emitOpcode(c.prog, vm.Otoaltstack)
|
||||
|
||||
emitInt(c.prog, int64(pos))
|
||||
emitOpcode(c.prog, vm.Opickitem)
|
||||
}
|
||||
|
||||
func (c *codegen) emitLoadStructField(sName, fName string) {
|
||||
strct := c.fctx.loadStruct(sName)
|
||||
pos := strct.loadField(fName)
|
||||
emitInt(c.prog, int64(pos))
|
||||
emitOpcode(c.prog, vm.Opickitem)
|
||||
}
|
||||
|
||||
func (c *codegen) emitStoreLocal(pos int) {
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
emitOpcode(c.prog, vm.Odup)
|
||||
emitOpcode(c.prog, vm.Otoaltstack)
|
||||
|
||||
if pos < 0 {
|
||||
log.Fatalf("invalid position to store local: %d", pos)
|
||||
}
|
||||
|
||||
emitInt(c.prog, int64(pos))
|
||||
emitInt(c.prog, 2)
|
||||
emitOpcode(c.prog, vm.Oroll)
|
||||
emitOpcode(c.prog, vm.Osetitem)
|
||||
}
|
||||
|
||||
func (c *codegen) emitStoreStructField(sName, fName string) {
|
||||
strct := c.fctx.loadStruct(sName)
|
||||
pos := strct.loadField(fName)
|
||||
emitInt(c.prog, int64(pos))
|
||||
emitOpcode(c.prog, vm.Orot)
|
||||
emitOpcode(c.prog, vm.Osetitem)
|
||||
}
|
||||
|
||||
func (c *codegen) convertFuncDecl(decl *ast.FuncDecl) {
|
||||
var (
|
||||
f *funcScope
|
||||
ok bool
|
||||
)
|
||||
|
||||
f, ok = c.funcs[decl.Name.Name]
|
||||
if ok {
|
||||
c.setLabel(f.label)
|
||||
} else {
|
||||
f = c.newFunc(decl)
|
||||
}
|
||||
c.fctx = f
|
||||
|
||||
emitInt(c.prog, f.stackSize())
|
||||
emitOpcode(c.prog, vm.Onewarray)
|
||||
emitOpcode(c.prog, vm.Otoaltstack)
|
||||
|
||||
// We need to handle methods, which in Go, is just syntactic sugar.
|
||||
// The method receiver will be passed in as first argument.
|
||||
// We check if this declaration has a receiver and load it into scope.
|
||||
//
|
||||
// FIXME: For now we will hard cast this to a struct. We can later finetune this
|
||||
// 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)
|
||||
l := c.fctx.newLocal(ident.Name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
}
|
||||
|
||||
// Load the arguments in scope.
|
||||
for _, arg := range decl.Type.Params.List {
|
||||
name := arg.Names[0].Name // for now.
|
||||
l := c.fctx.newLocal(name)
|
||||
c.emitStoreLocal(l)
|
||||
}
|
||||
|
||||
ast.Walk(c, decl.Body)
|
||||
}
|
||||
|
||||
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||
switch n := node.(type) {
|
||||
|
||||
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)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
switch n := t.X.(type) {
|
||||
case *ast.Ident:
|
||||
c.emitLoadLocal(n.Name) // load the struct
|
||||
c.emitStoreStructField(n.Name, t.Sel.Name) // store the field
|
||||
default:
|
||||
log.Fatal("nested selector assigns not supported yet")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
if len(n.Results) > 1 {
|
||||
log.Fatal("multiple returns not supported.")
|
||||
}
|
||||
|
||||
emitOpcode(c.prog, vm.Ojmp)
|
||||
emitOpcode(c.prog, vm.Opcode(0x03))
|
||||
emitOpcode(c.prog, vm.Opush0)
|
||||
|
||||
if len(n.Results) > 0 {
|
||||
ast.Walk(c, n.Results[0])
|
||||
}
|
||||
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
emitOpcode(c.prog, vm.Odrop)
|
||||
emitOpcode(c.prog, vm.Oret)
|
||||
return nil
|
||||
|
||||
case *ast.IfStmt:
|
||||
lIf := c.newLabel()
|
||||
lElse := c.newLabel()
|
||||
if n.Cond != nil {
|
||||
ast.Walk(c, n.Cond)
|
||||
emitJmp(c.prog, vm.Ojmpifnot, int16(lElse))
|
||||
}
|
||||
|
||||
c.setLabel(lIf)
|
||||
ast.Walk(c, n.Body)
|
||||
|
||||
if n.Else != nil {
|
||||
// TODO: handle else statements.
|
||||
// emitJmp(c.prog, vm.Ojmp, int16(lEnd))
|
||||
}
|
||||
c.setLabel(lElse)
|
||||
if n.Else != nil {
|
||||
ast.Walk(c, n.Else)
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ast.BasicLit:
|
||||
c.emitLoadConst(c.getTypeInfo(n))
|
||||
return nil
|
||||
|
||||
case *ast.Ident:
|
||||
if isIdentBool(n) {
|
||||
c.emitLoadConst(makeBoolFromIdent(n, c.typeInfo))
|
||||
} else {
|
||||
c.emitLoadLocal(n.Name)
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ast.CompositeLit:
|
||||
switch t := n.Type.(type) {
|
||||
case *ast.Ident:
|
||||
typ := c.typeInfo.ObjectOf(t).Type().Underlying()
|
||||
switch typ.(type) {
|
||||
case *types.Struct:
|
||||
c.convertStruct(n)
|
||||
}
|
||||
|
||||
default:
|
||||
ln := len(n.Elts)
|
||||
for i := ln - 1; i >= 0; i-- {
|
||||
c.emitLoadConst(c.getTypeInfo(n.Elts[i]))
|
||||
}
|
||||
emitInt(c.prog, int64(ln))
|
||||
emitOpcode(c.prog, vm.Opack)
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
switch n.Op {
|
||||
case token.LAND:
|
||||
ast.Walk(c, n.X)
|
||||
emitJmp(c.prog, vm.Ojmpifnot, int16(len(c.l)-1))
|
||||
ast.Walk(c, n.Y)
|
||||
return nil
|
||||
|
||||
case token.LOR:
|
||||
ast.Walk(c, n.X)
|
||||
emitJmp(c.prog, vm.Ojmpif, int16(len(c.l)-2))
|
||||
ast.Walk(c, n.Y)
|
||||
return nil
|
||||
|
||||
default:
|
||||
// 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
|
||||
// and needs no further action. e.g. x := 2 + 2 + 2 will be resolved to 6.
|
||||
if tinfo := c.getTypeInfo(n); tinfo.Value != nil {
|
||||
c.emitLoadConst(tinfo)
|
||||
return c
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
case *ast.CallExpr:
|
||||
var (
|
||||
f *funcScope
|
||||
ok bool
|
||||
numArgs = len(n.Args)
|
||||
)
|
||||
|
||||
switch fun := n.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
f, ok = c.funcs[fun.Name]
|
||||
if !ok {
|
||||
log.Fatalf("could not resolve function %s", fun.Name)
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
ast.Walk(c, fun.X)
|
||||
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++
|
||||
}
|
||||
|
||||
for _, arg := range n.Args {
|
||||
ast.Walk(c, arg)
|
||||
}
|
||||
if numArgs == 2 {
|
||||
emitOpcode(c.prog, vm.Oswap)
|
||||
}
|
||||
if numArgs == 3 {
|
||||
emitInt(c.prog, 2)
|
||||
emitOpcode(c.prog, vm.Oxswap)
|
||||
}
|
||||
|
||||
// c# compiler adds a NOP (0x61) before every function call. Dont think its relevant
|
||||
// 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))
|
||||
return nil
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
switch t := n.X.(type) {
|
||||
case *ast.Ident:
|
||||
c.emitLoadLocal(t.Name) // load the struct
|
||||
c.emitLoadStructField(t.Name, n.Sel.Name) // load the field
|
||||
default:
|
||||
log.Fatal("nested selectors not supported yet")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *codegen) convertStruct(lit *ast.CompositeLit) {
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
emitInt(c.prog, int64(len(lit.Elts)))
|
||||
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()
|
||||
|
||||
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)
|
||||
}
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
}
|
||||
|
||||
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
||||
f := newFuncScope(decl, c.newLabel())
|
||||
c.funcs[f.name] = f
|
||||
return f
|
||||
}
|
||||
|
||||
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.
|
||||
func CodeGen(f *ast.File, tInfo *types.Info) (*bytes.Buffer, error) {
|
||||
c := &codegen{
|
||||
prog: new(bytes.Buffer),
|
||||
l: []int{},
|
||||
funcs: map[string]*funcScope{},
|
||||
typeInfo: tInfo,
|
||||
}
|
||||
|
||||
var main *ast.FuncDecl
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
switch t := n.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if t.Name.Name == mainIdent {
|
||||
main = t
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if main == nil {
|
||||
log.Fatal("could not find func main. did you forgot to declare it?")
|
||||
}
|
||||
|
||||
c.resolveFuncDecls(f)
|
||||
c.convertFuncDecl(main)
|
||||
|
||||
for _, decl := range f.Decls {
|
||||
switch n := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if n.Name.Name != mainIdent {
|
||||
c.convertFuncDecl(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.writeJumps()
|
||||
|
||||
return c.prog, nil
|
||||
}
|
||||
|
||||
func (c *codegen) resolveFuncDecls(f *ast.File) {
|
||||
for _, decl := range f.Decls {
|
||||
switch n := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if n.Name.Name != mainIdent {
|
||||
c.newFunc(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *codegen) writeJumps() {
|
||||
b := c.prog.Bytes()
|
||||
for i, op := range b {
|
||||
j := i + 1
|
||||
switch vm.Opcode(op) {
|
||||
case vm.Ojmpifnot, vm.Ojmpif, vm.Ocall:
|
||||
index := binary.LittleEndian.Uint16(b[j : j+2])
|
||||
if int(index) > len(c.l) {
|
||||
continue
|
||||
}
|
||||
offset := uint16(c.l[index] - i)
|
||||
if offset < 0 {
|
||||
log.Fatalf("new offset is negative, table list %v", c.l)
|
||||
}
|
||||
binary.LittleEndian.PutUint16(b[j:j+2], offset)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,153 +2,44 @@ package compiler
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
outputExt = ".avm"
|
||||
// Identifier off the entry point function.
|
||||
mainIdent = "Main"
|
||||
)
|
||||
const fileExt = ".avm"
|
||||
|
||||
// CallContext represents more details on function calls in the program.
|
||||
// It stores the position off where the call happend along with the
|
||||
// function it called. The compiler will store all function calls, so
|
||||
// it can update them later.
|
||||
type CallContext struct {
|
||||
pos int
|
||||
funcName string
|
||||
// Options contains all the parameters that affect the behaviour of the compiler.
|
||||
type Options struct {
|
||||
// The extension of the output file default set to .avm
|
||||
Ext string
|
||||
|
||||
// The name of the output file.
|
||||
Outfile string
|
||||
|
||||
// Debug will output an hex encoded string of the generated bytecode.
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// A VarContext holds the info about the context of a variable in the program.
|
||||
type VarContext struct {
|
||||
name string
|
||||
tinfo types.TypeAndValue
|
||||
pos int
|
||||
}
|
||||
|
||||
func newVarContext(name string, tinfo types.TypeAndValue) *VarContext {
|
||||
return &VarContext{
|
||||
name: name,
|
||||
pos: -1,
|
||||
tinfo: tinfo,
|
||||
}
|
||||
}
|
||||
|
||||
// Compiler holds the output buffer of the compiled source.
|
||||
type Compiler struct {
|
||||
// Output extension of the file. Default .avm.
|
||||
OutputExt string
|
||||
|
||||
// scriptBuilder is responsible for all opcode writes.
|
||||
sb *ScriptBuilder
|
||||
|
||||
// map with all function contexts across the program.
|
||||
funcs map[string]*FuncContext
|
||||
|
||||
// list of function calls
|
||||
funcCalls []CallContext
|
||||
|
||||
// struct with info about decls, types, uses, ..
|
||||
typeInfo *types.Info
|
||||
}
|
||||
|
||||
// New returns a new compiler ready to compile smartcontracts.
|
||||
func New() *Compiler {
|
||||
return &Compiler{
|
||||
OutputExt: outputExt,
|
||||
sb: &ScriptBuilder{buf: new(bytes.Buffer)},
|
||||
funcs: map[string]*FuncContext{},
|
||||
funcCalls: []CallContext{},
|
||||
}
|
||||
}
|
||||
|
||||
// CompileSource will compile the source file into an avm format.
|
||||
func (c *Compiler) CompileSource(src string) error {
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Compile(file)
|
||||
}
|
||||
|
||||
// LoadConst load a constant, if storeLocal is true it will store it on the position
|
||||
// of the VarContext.
|
||||
func (c *Compiler) loadConst(ctx *VarContext, storeLocal bool) {
|
||||
switch t := ctx.tinfo.Type.(type) {
|
||||
case *types.Basic:
|
||||
switch t.Kind() {
|
||||
case types.Int:
|
||||
val, _ := constant.Int64Val(ctx.tinfo.Value)
|
||||
c.sb.emitPushInt(val)
|
||||
case types.String:
|
||||
val := constant.StringVal(ctx.tinfo.Value)
|
||||
c.sb.emitPushString(val)
|
||||
case types.Bool, types.UntypedBool:
|
||||
val := constant.BoolVal(ctx.tinfo.Value)
|
||||
c.sb.emitPushBool(val)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("compiler don't know how to handle this => %v", ctx)
|
||||
}
|
||||
|
||||
if storeLocal {
|
||||
c.storeLocal(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Load a local variable. The position of the VarContext is used to retrieve from
|
||||
// that position.
|
||||
func (c *Compiler) loadLocal(ctx *VarContext) {
|
||||
pos := int64(ctx.pos)
|
||||
if pos < 0 {
|
||||
log.Fatalf("want to load local %v but got invalid position => %d <=", ctx, pos)
|
||||
}
|
||||
|
||||
c.sb.emitPush(vm.OpFromAltStack)
|
||||
c.sb.emitPush(vm.OpDup)
|
||||
c.sb.emitPush(vm.OpToAltStack)
|
||||
|
||||
// push it's index on the stack
|
||||
c.sb.emitPushInt(pos)
|
||||
c.sb.emitPush(vm.OpPickItem)
|
||||
}
|
||||
|
||||
// Store a local variable on the stack. The position of the VarContext is used
|
||||
// to store at that position.
|
||||
func (c *Compiler) storeLocal(vctx *VarContext) {
|
||||
c.sb.emitPush(vm.OpFromAltStack)
|
||||
c.sb.emitPush(vm.OpDup)
|
||||
c.sb.emitPush(vm.OpToAltStack)
|
||||
|
||||
pos := int64(vctx.pos)
|
||||
if pos < 0 {
|
||||
log.Fatalf("want to store local %v but got invalid positionl => %d", vctx, pos)
|
||||
}
|
||||
|
||||
c.sb.emitPushInt(pos)
|
||||
c.sb.emitPushInt(2)
|
||||
c.sb.emitPush(vm.OpRoll)
|
||||
c.sb.emitPush(vm.OpSetItem)
|
||||
}
|
||||
|
||||
// Compile will compile from r into an avm format.
|
||||
func (c *Compiler) Compile(r io.Reader) error {
|
||||
// Compile compiles a Go program into bytecode that can run on the NEO virtual machine.
|
||||
func Compile(input io.Reader, o *Options) ([]byte, error) {
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "", r, 0)
|
||||
f, err := parser.ParseFile(fset, "", input, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := types.Config{Importer: importer.Default()}
|
||||
|
@ -161,343 +52,67 @@ func (c *Compiler) Compile(r io.Reader) error {
|
|||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
}
|
||||
|
||||
c.typeInfo = typeInfo
|
||||
|
||||
// Typechecker
|
||||
_, err = conf.Check("", fset, []*ast.File{f}, typeInfo)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var main *ast.FuncDecl
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
switch t := n.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if t.Name.Name == mainIdent {
|
||||
main = t
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if main == nil {
|
||||
log.Fatal("could not find func main. did you forgot to declare it?")
|
||||
buf, err := CodeGen(f, typeInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.resolveFuncDecls(f)
|
||||
c.convertFuncDecl(main)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Start building all declarations
|
||||
for _, decl := range f.Decls {
|
||||
switch t := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
case *ast.FuncDecl:
|
||||
if t.Name.Name != mainIdent {
|
||||
c.convertFuncDecl(t)
|
||||
// CompileAndSave will compile and save the file to disk.
|
||||
func CompileAndSave(src string, o *Options) error {
|
||||
if len(o.Outfile) == 0 {
|
||||
if !strings.HasSuffix(src, ".go") {
|
||||
return errors.New("not a Go file")
|
||||
}
|
||||
o.Outfile = strings.TrimSuffix(src, ".go")
|
||||
}
|
||||
if len(o.Ext) == 0 {
|
||||
o.Ext = fileExt
|
||||
}
|
||||
b, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err = Compile(bytes.NewReader(b), o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.Debug {
|
||||
log.Println(hex.EncodeToString(b))
|
||||
}
|
||||
|
||||
// update all local function calls.
|
||||
c.updateFuncCalls()
|
||||
out := fmt.Sprintf("%s.%s", o.Outfile, o.Ext)
|
||||
return ioutil.WriteFile(out, b, os.ModePerm)
|
||||
}
|
||||
|
||||
// DumpOpcode compiles the program and dumps the opcode in a user friendly format.
|
||||
func DumpOpcode(src string) error {
|
||||
b, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err = Compile(bytes.NewReader(b), &Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
||||
fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t")
|
||||
for i := 0; i < len(b); i++ {
|
||||
fmt.Fprintf(w, "%d\t0x%2x\t%s\t\n", i, b[i], vm.Opcode(b[i]))
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateFuncCalls will update all local function calls that occured the program.
|
||||
func (c *Compiler) updateFuncCalls() {
|
||||
for _, ctx := range c.funcCalls {
|
||||
fun, ok := c.funcs[ctx.funcName]
|
||||
if !ok {
|
||||
log.Fatalf("could not resolve function %s", ctx.funcName)
|
||||
}
|
||||
// pos is the position of the call op, we need to add 1 to get the
|
||||
// start of the label.
|
||||
// for calculating the correct offset we need to subtract the target label
|
||||
// with the position the call occured.
|
||||
offset := fun.label - int16(ctx.pos)
|
||||
c.sb.updatePushCall(ctx.pos+1, offset)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) resolveFuncDecls(f *ast.File) {
|
||||
for _, decl := range f.Decls {
|
||||
switch t := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
case *ast.FuncDecl:
|
||||
if t.Name.Name != mainIdent {
|
||||
c.funcs[t.Name.Name] = newFuncContext(t, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) convertFuncDecl(decl *ast.FuncDecl) {
|
||||
ctx := newFuncContext(decl, c.currentPos())
|
||||
c.funcs[ctx.name] = ctx
|
||||
|
||||
// We need to write the the total stack size of the function first.
|
||||
// That size is the number of arguments + body operations that will be
|
||||
// pushed on the stack
|
||||
c.sb.emitPushInt(ctx.numStackOps())
|
||||
c.sb.emitPush(vm.OpNewArray)
|
||||
c.sb.emitPush(vm.OpToAltStack)
|
||||
|
||||
// Load the arguments into scope.
|
||||
for _, arg := range decl.Type.Params.List {
|
||||
name := arg.Names[0].Name
|
||||
ctx.args[name] = true
|
||||
vctx := ctx.newConst(name, c.getTypeInfo(arg.Type), true)
|
||||
c.storeLocal(vctx)
|
||||
}
|
||||
|
||||
for _, stmt := range decl.Body.List {
|
||||
c.convertStmt(ctx, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) convertStmt(fctx *FuncContext, stmt ast.Stmt) {
|
||||
switch t := stmt.(type) {
|
||||
case *ast.AssignStmt:
|
||||
for i := 0; i < len(t.Lhs); i++ {
|
||||
lhs := t.Lhs[i].(*ast.Ident)
|
||||
|
||||
switch rhs := t.Rhs[i].(type) {
|
||||
case *ast.BasicLit:
|
||||
vctx := fctx.newConst(lhs.Name, c.getTypeInfo(t.Rhs[i]), true)
|
||||
c.loadConst(vctx, true)
|
||||
|
||||
case *ast.CompositeLit:
|
||||
// Write constants in reverse order on the stack.
|
||||
n := len(rhs.Elts)
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
vctx := fctx.newConst("", c.getTypeInfo(rhs.Elts[i]), false)
|
||||
c.loadConst(vctx, false)
|
||||
}
|
||||
|
||||
c.sb.emitPushInt(int64(n))
|
||||
c.sb.emitPush(vm.OpPack)
|
||||
|
||||
vctx := fctx.newConst(lhs.Name, c.getTypeInfo(rhs), true)
|
||||
c.storeLocal(vctx)
|
||||
|
||||
case *ast.Ident:
|
||||
if isIdentBool(rhs) {
|
||||
vctx := fctx.newConst(lhs.Name, makeBoolFromIdent(rhs, c.typeInfo), true)
|
||||
c.loadConst(vctx, true)
|
||||
continue
|
||||
}
|
||||
|
||||
knownCtx := fctx.getContext(rhs.Name)
|
||||
c.loadLocal(knownCtx)
|
||||
newCtx := fctx.newConst(lhs.Name, c.getTypeInfo(rhs), true)
|
||||
c.storeLocal(newCtx)
|
||||
|
||||
default:
|
||||
c.convertExpr(fctx, t.Rhs[i])
|
||||
vctx := fctx.newConst(lhs.Name, c.getTypeInfo(t.Rhs[i]), true)
|
||||
c.storeLocal(vctx)
|
||||
}
|
||||
}
|
||||
|
||||
//Due to the design of the orginal VM, multiple return are not supported.
|
||||
case *ast.ReturnStmt:
|
||||
if len(t.Results) > 1 {
|
||||
log.Fatal("multiple returns not supported.")
|
||||
}
|
||||
|
||||
c.sb.emitPush(vm.OpJMP)
|
||||
c.sb.emitPush(vm.OpCode(0x03))
|
||||
c.sb.emitPush(vm.OpPush0)
|
||||
|
||||
c.convertExpr(fctx, t.Results[0])
|
||||
|
||||
c.sb.emitPush(vm.OpNOP)
|
||||
c.sb.emitPush(vm.OpFromAltStack)
|
||||
c.sb.emitPush(vm.OpDrop)
|
||||
c.sb.emitPush(vm.OpRET)
|
||||
|
||||
// TODO: this needs a rewrite ASAP.
|
||||
case *ast.IfStmt:
|
||||
c.convertExpr(fctx, t.Cond)
|
||||
|
||||
binExpr, ok := t.Cond.(*ast.BinaryExpr)
|
||||
if ok && binExpr.Op != token.LAND && binExpr.Op != token.LOR {
|
||||
// use a placeholder for the label.
|
||||
c.sb.emitJump(vm.OpJMPIFNOT, int16(0))
|
||||
// track our offset to update later subtract sizeOf int16.
|
||||
offset := int(c.currentPos()) - 2
|
||||
|
||||
defer func(offset int) {
|
||||
jumpTo := c.currentPos() + 1 - int16(offset)
|
||||
c.sb.updateJmpLabel(jumpTo, offset)
|
||||
}(offset)
|
||||
}
|
||||
|
||||
labelBeforeBlock := c.currentPos()
|
||||
// Process the block.
|
||||
for _, stmt := range t.Body.List {
|
||||
c.convertStmt(fctx, stmt)
|
||||
}
|
||||
|
||||
// if there are any labels we need to update.
|
||||
if len(fctx.jumpLabels) > 0 {
|
||||
for _, label := range fctx.jumpLabels {
|
||||
var pos int16
|
||||
if label.op == vm.OpJMPIF {
|
||||
pos = labelBeforeBlock + 1
|
||||
} else {
|
||||
pos = c.currentPos() + 1
|
||||
}
|
||||
jumpTo := pos - int16(label.offset)
|
||||
c.sb.updateJmpLabel(jumpTo, label.offset)
|
||||
}
|
||||
fctx.jumpLabels = []jumpLabel{}
|
||||
}
|
||||
|
||||
default:
|
||||
log.Fatalf("compiler has not implemented this statement => %v", reflect.TypeOf(t))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) convertExpr(fctx *FuncContext, expr ast.Expr) {
|
||||
switch t := expr.(type) {
|
||||
case *ast.BasicLit:
|
||||
vctx := fctx.newConst("", c.getTypeInfo(t), false)
|
||||
c.loadConst(vctx, false)
|
||||
|
||||
case *ast.Ident:
|
||||
if isIdentBool(t) {
|
||||
vctx := fctx.newConst(t.Name, makeBoolFromIdent(t, c.typeInfo), false)
|
||||
c.loadConst(vctx, false)
|
||||
return
|
||||
}
|
||||
if fctx.isArgument(t.Name) {
|
||||
vctx := fctx.getContext(t.Name)
|
||||
c.loadLocal(vctx)
|
||||
return
|
||||
}
|
||||
vctx := fctx.getContext(t.Name)
|
||||
c.loadLocal(vctx)
|
||||
|
||||
case *ast.CallExpr:
|
||||
fun := t.Fun.(*ast.Ident)
|
||||
fctx, ok := c.funcs[fun.Name]
|
||||
if !ok {
|
||||
log.Fatalf("could not resolve func %s", fun.Name)
|
||||
}
|
||||
|
||||
// handle the passed arguments.
|
||||
for _, arg := range t.Args {
|
||||
vctx := fctx.newConst("", c.getTypeInfo(arg), false)
|
||||
c.loadLocal(vctx)
|
||||
}
|
||||
|
||||
// c# compiler adds a NOP (0x61) before every function call. Dont think its relevant
|
||||
// and we could easily removed it, but to be consistent with the original compiler I
|
||||
// will put them in. ^^
|
||||
c.sb.emitPush(vm.OpNOP)
|
||||
|
||||
c.funcCalls = append(c.funcCalls, CallContext{int(c.currentPos()), fun.Name})
|
||||
c.sb.emitPushCall(0) // placeholder, update later.
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
if t.Op == token.LAND || t.Op == token.LOR {
|
||||
c.convertExpr(fctx, t.X)
|
||||
|
||||
opJMP := vm.OpJMPIFNOT
|
||||
if t.Op == token.LOR {
|
||||
opJMP = vm.OpJMPIF
|
||||
}
|
||||
|
||||
if e, ok := t.X.(*ast.BinaryExpr); ok && e.Op != token.LAND && e.Op != token.LOR {
|
||||
c.sb.emitJump(opJMP, int16(0))
|
||||
fctx.addJump(opJMP, int(c.currentPos())-2)
|
||||
}
|
||||
|
||||
c.convertExpr(fctx, t.Y)
|
||||
c.sb.emitJump(vm.OpJMPIFNOT, int16(0))
|
||||
fctx.addJump(vm.OpJMPIFNOT, int(c.currentPos())-2)
|
||||
c.convertToken(t.Op)
|
||||
return
|
||||
}
|
||||
|
||||
// The AST package resolves all basic literals for us. If the typeinfo.Value is not nil
|
||||
// we know that the bin expr is resolved and needs no further action.
|
||||
// e.g. x := 2 + 2 + 2 will be resolved to 6.
|
||||
if tinfo := c.getTypeInfo(t); tinfo.Value != nil {
|
||||
vctx := fctx.newConst("", tinfo, false)
|
||||
c.loadConst(vctx, false)
|
||||
return
|
||||
}
|
||||
|
||||
c.convertExpr(fctx, t.X)
|
||||
c.convertExpr(fctx, t.Y)
|
||||
c.convertToken(t.Op)
|
||||
|
||||
default:
|
||||
log.Fatalf("compiler has not implemented this expr => %v", reflect.TypeOf(t))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) convertToken(tok token.Token) {
|
||||
switch tok {
|
||||
case token.ADD:
|
||||
c.sb.emitPush(vm.OpAdd)
|
||||
case token.SUB:
|
||||
c.sb.emitPush(vm.OpSub)
|
||||
case token.MUL:
|
||||
c.sb.emitPush(vm.OpMul)
|
||||
case token.QUO:
|
||||
c.sb.emitPush(vm.OpDiv)
|
||||
case token.LSS:
|
||||
c.sb.emitPush(vm.OpLT)
|
||||
case token.LEQ:
|
||||
c.sb.emitPush(vm.OpLTE)
|
||||
case token.GTR:
|
||||
c.sb.emitPush(vm.OpGT)
|
||||
case token.GEQ:
|
||||
c.sb.emitPush(vm.OpGTE)
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeInfo return TypeAndValue for the given expression. If it could not resolve
|
||||
// the type value and type will be NIL.
|
||||
func (c *Compiler) getTypeInfo(expr ast.Expr) types.TypeAndValue {
|
||||
return c.typeInfo.Types[expr]
|
||||
}
|
||||
|
||||
// currentPos return the current position (address) of the latest opcode.
|
||||
func (c *Compiler) currentPos() int16 {
|
||||
return int16(c.sb.buf.Len())
|
||||
}
|
||||
|
||||
// Buffer returns the buffer of the builder as a io.Reader.
|
||||
func (c *Compiler) Buffer() *bytes.Buffer {
|
||||
return c.sb.buf
|
||||
}
|
||||
|
||||
// DumpOpcode dumps the current buffer, formatted with index, hex and opcode.
|
||||
func (c *Compiler) DumpOpcode() {
|
||||
c.sb.dumpOpcode()
|
||||
}
|
||||
|
||||
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"
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
}
|
||||
|
|
101
pkg/vm/compiler/emit.go
Normal file
101
pkg/vm/compiler/emit.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
func emit(w *bytes.Buffer, op vm.Opcode, b []byte) error {
|
||||
if err := w.WriteByte(byte(op)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := w.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func emitOpcode(w *bytes.Buffer, op vm.Opcode) error {
|
||||
return w.WriteByte(byte(op))
|
||||
}
|
||||
|
||||
func emitBool(w *bytes.Buffer, ok bool) error {
|
||||
if ok {
|
||||
return emitOpcode(w, vm.Opusht)
|
||||
}
|
||||
return emitOpcode(w, vm.Opushf)
|
||||
}
|
||||
|
||||
func emitInt(w *bytes.Buffer, i int64) error {
|
||||
if i == -1 {
|
||||
return emitOpcode(w, vm.Opushm1)
|
||||
}
|
||||
if i == 0 {
|
||||
return emitOpcode(w, vm.Opushf)
|
||||
}
|
||||
if i > 0 && i < 16 {
|
||||
val := vm.Opcode((int(vm.Opush1) - 1 + int(i)))
|
||||
return emitOpcode(w, val)
|
||||
}
|
||||
|
||||
bInt := big.NewInt(i)
|
||||
val := util.ToArrayReverse(bInt.Bytes())
|
||||
return emitBytes(w, val)
|
||||
}
|
||||
|
||||
func emitString(w *bytes.Buffer, s string) error {
|
||||
return emitBytes(w, []byte(s))
|
||||
}
|
||||
|
||||
func emitBytes(w *bytes.Buffer, b []byte) error {
|
||||
var (
|
||||
err error
|
||||
n = len(b)
|
||||
)
|
||||
|
||||
if n == 0 {
|
||||
return errors.New("cannot emit 0 bytes")
|
||||
}
|
||||
if n <= int(vm.Opushbytes75) {
|
||||
return emit(w, vm.Opcode(n), b)
|
||||
} else if n < 0x100 {
|
||||
err = emit(w, vm.Opushdata1, []byte{byte(n)})
|
||||
} else if n < 0x10000 {
|
||||
buf := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(buf, uint16(n))
|
||||
err = emit(w, vm.Opushdata2, buf)
|
||||
} else {
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, uint32(n))
|
||||
err = emit(w, vm.Opushdata4, buf)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func emitCall(w *bytes.Buffer, op vm.Opcode, label int16) error {
|
||||
return emitJmp(w, op, label)
|
||||
}
|
||||
|
||||
func emitJmp(w *bytes.Buffer, op vm.Opcode, label int16) error {
|
||||
if !isOpcodeJmp(op) {
|
||||
return fmt.Errorf("opcode %s is not a jump or call type", op)
|
||||
}
|
||||
buf := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(buf, uint16(label))
|
||||
return emit(w, op, buf)
|
||||
}
|
||||
|
||||
func isOpcodeJmp(op vm.Opcode) bool {
|
||||
if op == vm.Ojmp || op == vm.Ojmpifnot || op == vm.Ojmpif || op == vm.Ocall {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"log"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
type jumpLabel struct {
|
||||
offset int
|
||||
op vm.OpCode
|
||||
}
|
||||
|
||||
// A FuncContext represents details about a function in the program along withs its variables.
|
||||
type FuncContext struct {
|
||||
// The declaration tree of this function.
|
||||
decl *ast.FuncDecl
|
||||
// Identifier (name of the function in the program).
|
||||
name string
|
||||
// The scope of the function.
|
||||
scope map[string]*VarContext
|
||||
// Arguments of the function.
|
||||
args map[string]bool
|
||||
// Address (label) where the compiler can find this function when someone calls it.
|
||||
label int16
|
||||
// Counter for stored local variables.
|
||||
i int
|
||||
// This needs refactor along with the (if stmt)
|
||||
jumpLabels []jumpLabel
|
||||
}
|
||||
|
||||
func (f *FuncContext) addJump(op vm.OpCode, offset int) {
|
||||
f.jumpLabels = append(f.jumpLabels, jumpLabel{offset, op})
|
||||
}
|
||||
|
||||
func newFuncContext(decl *ast.FuncDecl, label int16) *FuncContext {
|
||||
return &FuncContext{
|
||||
decl: decl,
|
||||
label: int16(label),
|
||||
name: decl.Name.Name,
|
||||
scope: map[string]*VarContext{},
|
||||
args: map[string]bool{},
|
||||
jumpLabels: []jumpLabel{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FuncContext) newConst(name string, t types.TypeAndValue, needStore bool) *VarContext {
|
||||
ctx := &VarContext{
|
||||
name: name,
|
||||
tinfo: t,
|
||||
}
|
||||
if needStore {
|
||||
f.storeContext(ctx)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (f *FuncContext) numStackOps() int64 {
|
||||
ops := 0
|
||||
ast.Inspect(f.decl, func(n ast.Node) bool {
|
||||
switch n.(type) {
|
||||
case *ast.AssignStmt, *ast.ReturnStmt, *ast.IfStmt:
|
||||
ops++
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
numArgs := len(f.decl.Type.Params.List)
|
||||
return int64(ops + numArgs)
|
||||
}
|
||||
|
||||
func (f *FuncContext) storeContext(ctx *VarContext) {
|
||||
ctx.pos = f.i
|
||||
f.scope[ctx.name] = ctx
|
||||
f.i++
|
||||
}
|
||||
|
||||
func (f *FuncContext) getContext(name string) *VarContext {
|
||||
ctx, ok := f.scope[name]
|
||||
if !ok {
|
||||
log.Fatalf("could not resolve variable %s", name)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (f *FuncContext) isRegistered(ctx *VarContext) bool {
|
||||
_, ok := f.scope[ctx.name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (f *FuncContext) isArgument(name string) bool {
|
||||
_, ok := f.args[name]
|
||||
return ok
|
||||
}
|
89
pkg/vm/compiler/func_scope.go
Normal file
89
pkg/vm/compiler/func_scope.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"log"
|
||||
)
|
||||
|
||||
// A funcScope represents a scope within the function context.
|
||||
// It holds al the local variables along with the initialized struct positions.
|
||||
type funcScope struct {
|
||||
// function identifier
|
||||
name string
|
||||
|
||||
// The declaration of the function in the AST
|
||||
decl *ast.FuncDecl
|
||||
|
||||
// program label of the function
|
||||
label int
|
||||
|
||||
// local scope of the function
|
||||
scope map[string]int
|
||||
|
||||
// mapping of structs positions with their scope
|
||||
structs map[int]*structScope
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *funcScope) stackSize() int64 {
|
||||
size := 0
|
||||
ast.Inspect(c.decl, func(n ast.Node) bool {
|
||||
switch n.(type) {
|
||||
case *ast.AssignStmt, *ast.ReturnStmt, *ast.IfStmt:
|
||||
size++
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
numArgs := len(c.decl.Type.Params.List)
|
||||
// Also take care of struct methods recv: e.g. (t Token).Foo().
|
||||
if c.decl.Recv != nil {
|
||||
numArgs += len(c.decl.Recv.List)
|
||||
}
|
||||
return int64(size + numArgs)
|
||||
}
|
||||
|
||||
func (c *funcScope) newStruct() *structScope {
|
||||
strct := newStructScope()
|
||||
c.structs[len(c.scope)] = strct
|
||||
return strct
|
||||
}
|
||||
|
||||
func (c *funcScope) loadStruct(name string) *structScope {
|
||||
l := c.loadLocal(name)
|
||||
strct, ok := c.structs[l]
|
||||
if !ok {
|
||||
log.Fatalf("could not resolve struct %s", name)
|
||||
}
|
||||
return strct
|
||||
}
|
||||
|
||||
// newLocal creates a new local variable into the scope of the function.
|
||||
func (c *funcScope) newLocal(name string) int {
|
||||
c.i++
|
||||
c.scope[name] = c.i
|
||||
return c.i
|
||||
}
|
||||
|
||||
// loadLocal loads the position of a local variable inside the scope of the function.
|
||||
func (c *funcScope) loadLocal(name string) int {
|
||||
i, ok := c.scope[name]
|
||||
if !ok {
|
||||
// should emit a compiler warning.
|
||||
return c.newLocal(name)
|
||||
}
|
||||
return i
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
// ScriptBuilder generates bytecode and will write all
|
||||
// generated bytecode into its internal buffer.
|
||||
type ScriptBuilder struct {
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emit(op vm.OpCode, b []byte) error {
|
||||
if err := sb.buf.WriteByte(byte(op)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := sb.buf.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emitPush(op vm.OpCode) error {
|
||||
return sb.buf.WriteByte(byte(op))
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emitPushBool(b bool) error {
|
||||
if b {
|
||||
return sb.emitPush(vm.OpPushT)
|
||||
}
|
||||
return sb.emitPush(vm.OpPushF)
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emitPushInt(i int64) error {
|
||||
if i == -1 {
|
||||
return sb.emitPush(vm.OpPushM1)
|
||||
}
|
||||
if i == 0 {
|
||||
return sb.emitPush(vm.OpPushF)
|
||||
}
|
||||
if i > 0 && i < 16 {
|
||||
val := vm.OpCode((int(vm.OpPush1) - 1 + int(i)))
|
||||
return sb.emitPush(val)
|
||||
}
|
||||
|
||||
bInt := big.NewInt(i)
|
||||
val := util.ToArrayReverse(bInt.Bytes())
|
||||
return sb.emitPushArray(val)
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emitPushArray(b []byte) error {
|
||||
var (
|
||||
err error
|
||||
n = len(b)
|
||||
)
|
||||
|
||||
if n == 0 {
|
||||
return errors.New("0 bytes given in pushArray")
|
||||
}
|
||||
if n <= int(vm.OpPushBytes75) {
|
||||
return sb.emit(vm.OpCode(n), b)
|
||||
} else if n < 0x100 {
|
||||
err = sb.emit(vm.OpPushData1, []byte{byte(n)})
|
||||
} else if n < 0x10000 {
|
||||
buf := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(buf, uint16(n))
|
||||
err = sb.emit(vm.OpPushData2, buf)
|
||||
} else {
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, uint32(n))
|
||||
err = sb.emit(vm.OpPushData4, buf)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sb.buf.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emitPushString(str string) error {
|
||||
return sb.emitPushArray([]byte(str))
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emitSysCall(api string) error {
|
||||
lenAPI := len(api)
|
||||
if lenAPI == 0 {
|
||||
return errors.New("syscall argument cant be 0")
|
||||
}
|
||||
if lenAPI > 252 {
|
||||
return fmt.Errorf("invalid syscall argument: %s", api)
|
||||
}
|
||||
|
||||
bapi := []byte(api)
|
||||
args := make([]byte, lenAPI+1)
|
||||
args[0] = byte(lenAPI)
|
||||
copy(args, bapi[1:])
|
||||
return sb.emit(vm.OpSysCall, args)
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emitPushCall(offset int16) error {
|
||||
buf := new(bytes.Buffer)
|
||||
binary.Write(buf, binary.LittleEndian, offset)
|
||||
return sb.emit(vm.OpCall, buf.Bytes())
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) emitJump(op vm.OpCode, offset int16) error {
|
||||
if op != vm.OpJMP && op != vm.OpJMPIF && op != vm.OpJMPIFNOT && op != vm.OpCall {
|
||||
return fmt.Errorf("invalid jump opcode: %v", op)
|
||||
}
|
||||
buf := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(buf, uint16(offset))
|
||||
return sb.emit(op, buf)
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) updateJmpLabel(label int16, offset int) error {
|
||||
sizeOfInt16 := 2
|
||||
if sizeOfInt16+offset >= sb.buf.Len() {
|
||||
return fmt.Errorf("cannot update label at offset %d", offset)
|
||||
}
|
||||
|
||||
b := make([]byte, sizeOfInt16)
|
||||
binary.LittleEndian.PutUint16(b, uint16(label))
|
||||
buf := sb.buf.Bytes()
|
||||
copy(buf[offset:offset+sizeOfInt16], b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) updatePushCall(offset int, label int16) {
|
||||
b := new(bytes.Buffer)
|
||||
binary.Write(b, binary.LittleEndian, label)
|
||||
|
||||
buf := sb.buf.Bytes()
|
||||
copy(buf[offset:offset+2], b.Bytes())
|
||||
}
|
||||
|
||||
func (sb *ScriptBuilder) dumpOpcode() {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
||||
buf := sb.buf.Bytes()
|
||||
|
||||
fmt.Fprintln(w, "INDEX\tOPCODE\tDESC")
|
||||
for i := 0; i < len(buf); i++ {
|
||||
fmt.Fprintf(w, "%d\t0x%2x\t%s\n", i, buf[i], vm.OpCode(buf[i]))
|
||||
}
|
||||
w.Flush()
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
func TestEmitPush(t *testing.T) {
|
||||
sb := &ScriptBuilder{buf: new(bytes.Buffer)}
|
||||
|
||||
if err := sb.emitPush(vm.OpPush1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sb.buf.Len() != 1 {
|
||||
t.Fatalf("expect buffer len of 1 got %d", sb.buf.Len())
|
||||
}
|
||||
}
|
||||
func TestEmitPushIntNeg(t *testing.T) {
|
||||
sb := &ScriptBuilder{buf: new(bytes.Buffer)}
|
||||
val := -1
|
||||
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := vm.OpPushM1, vm.OpCode(sb.buf.Bytes()[0]); want != have {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmitPushInt0(t *testing.T) {
|
||||
sb := &ScriptBuilder{buf: new(bytes.Buffer)}
|
||||
val := 0
|
||||
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := vm.OpPushF, vm.OpCode(sb.buf.Bytes()[0]); want != have {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmitPushInt1(t *testing.T) {
|
||||
sb := &ScriptBuilder{buf: new(bytes.Buffer)}
|
||||
val := 1
|
||||
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := vm.OpPush1, vm.OpCode(sb.buf.Bytes()[0]); want != have {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmitPushInt100(t *testing.T) {
|
||||
x := 100
|
||||
bigx := big.NewInt(int64(x))
|
||||
t.Log(bigx.Bytes())
|
||||
|
||||
sb := &ScriptBuilder{buf: new(bytes.Buffer)}
|
||||
val := 100
|
||||
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// len = 1
|
||||
if want, have := byte(0x01), byte(sb.buf.Bytes()[0]); want != have {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
if want, have := byte(0x64), byte(sb.buf.Bytes()[1]); want != have {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmitPush1000(t *testing.T) {
|
||||
sb := &ScriptBuilder{buf: new(bytes.Buffer)}
|
||||
val := 1000
|
||||
if err := sb.emitPushInt(int64(val)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bInt := big.NewInt(int64(val))
|
||||
if want, have := byte(len(bInt.Bytes())), byte(sb.buf.Bytes()[0]); want != have {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
want := util.ToArrayReverse(bInt.Bytes()) // reverse
|
||||
have := sb.buf.Bytes()[1:]
|
||||
if bytes.Compare(want, have) != 0 {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmitPushString(t *testing.T) {
|
||||
sb := &ScriptBuilder{buf: new(bytes.Buffer)}
|
||||
str := "anthdm"
|
||||
if err := sb.emitPushString(str); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := byte(len(str)), sb.buf.Bytes()[0]; want != have {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
want, have := []byte(str), sb.buf.Bytes()[1:]
|
||||
if bytes.Compare(want, have) != 0 {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
}
|
51
pkg/vm/compiler/struct_scope.go
Normal file
51
pkg/vm/compiler/struct_scope.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"log"
|
||||
)
|
||||
|
||||
// A structScope holds the positions for it's fields. Struct fields have different
|
||||
// positions then local variables in any scope.
|
||||
type structScope struct {
|
||||
// identifier of the initialized struct in the program.
|
||||
name string
|
||||
|
||||
// a mapping of field identifier and its position.
|
||||
fields map[string]int
|
||||
}
|
||||
|
||||
func newStructScope() *structScope {
|
||||
return &structScope{
|
||||
fields: map[string]int{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *structScope) newField(name string) int {
|
||||
i := len(s.fields)
|
||||
s.fields[name] = i
|
||||
return i
|
||||
}
|
||||
|
||||
func (s *structScope) loadField(name string) int {
|
||||
i, ok := s.fields[name]
|
||||
if !ok {
|
||||
log.Fatalf("could not resolve field name %s for struct %s", name, s.name)
|
||||
}
|
||||
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)
|
||||
}
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
s.newField(t.Field(i).Name())
|
||||
}
|
||||
}
|
26
pkg/vm/compiler/tests/array_test.go
Normal file
26
pkg/vm/compiler/tests/array_test.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package compiler_test
|
||||
|
||||
var arrayTestCases = []testCase{
|
||||
{
|
||||
"assign int array",
|
||||
`
|
||||
package foo
|
||||
func Main() []int {
|
||||
x := []int{1, 2, 3}
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"52c56b53525153c16c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"assign string array",
|
||||
`
|
||||
package foo
|
||||
func Main() []string {
|
||||
x := []string{"foo", "bar", "foobar"}
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"52c56b06666f6f6261720362617203666f6f53c16c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
}
|
31
pkg/vm/compiler/tests/assign_test.go
Normal file
31
pkg/vm/compiler/tests/assign_test.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package compiler_test
|
||||
|
||||
var assignTestCases = []testCase{
|
||||
{
|
||||
"chain define",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 4
|
||||
y := x
|
||||
z := y
|
||||
foo := z
|
||||
bar := foo
|
||||
return bar
|
||||
}
|
||||
`,
|
||||
"56c56b546c766b00527ac46c766b00c36c766b51527ac46c766b51c36c766b52527ac46c766b52c36c766b53527ac46c766b53c36c766b54527ac46203006c766b54c3616c7566",
|
||||
},
|
||||
{
|
||||
"simple assign",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 4
|
||||
x = 8
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"53c56b546c766b00527ac4586c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
}
|
15
pkg/vm/compiler/tests/bool_test.go
Normal file
15
pkg/vm/compiler/tests/bool_test.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package compiler_test
|
||||
|
||||
var boolTestCases = []testCase{
|
||||
{
|
||||
"bool assign",
|
||||
`
|
||||
package foo
|
||||
func Main() bool {
|
||||
x := true
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"52c56b516c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
}
|
|
@ -21,14 +21,18 @@ type testCase struct {
|
|||
|
||||
func TestAllCases(t *testing.T) {
|
||||
testCases := []testCase{}
|
||||
testCases = append(testCases, assignTestCases...)
|
||||
testCases = append(testCases, arrayTestCases...)
|
||||
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, functionCallTestCases...)
|
||||
|
||||
for _, tc := range testCases {
|
||||
c := compiler.New()
|
||||
if err := c.Compile(strings.NewReader(tc.src)); err != nil {
|
||||
b, err := compiler.Compile(strings.NewReader(tc.src), &compiler.Options{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -37,10 +41,10 @@ func TestAllCases(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if bytes.Compare(c.Buffer().Bytes(), expectedResult) != 0 {
|
||||
t.Log(hex.EncodeToString(c.Buffer().Bytes()))
|
||||
if bytes.Compare(b, expectedResult) != 0 {
|
||||
t.Log(hex.EncodeToString(b))
|
||||
want, _ := hex.DecodeString(tc.result)
|
||||
dumpOpCodeSideBySide(c.Buffer().Bytes(), want)
|
||||
dumpOpCodeSideBySide(b, want)
|
||||
t.Fatalf("compiling %s failed", tc.name)
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +63,7 @@ func dumpOpCodeSideBySide(have, want []byte) {
|
|||
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]), want[i], vm.Opcode(want[i]), diff)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
|
|
@ -58,4 +58,19 @@ var functionCallTestCases = []testCase{
|
|||
`,
|
||||
"53c56b5a6c766b00527ac46c766b00c3616516006c766b51527ac46203006c766b51c3616c756653c56b6c766b00527ac4586c766b51527ac46203006c766b00c36c766b51c393616c7566",
|
||||
},
|
||||
{
|
||||
"function call with multiple arguments",
|
||||
`
|
||||
package testcase
|
||||
func Main() int {
|
||||
x := addIntegers(2, 4)
|
||||
return x
|
||||
}
|
||||
|
||||
func addIntegers(x int, y int) int {
|
||||
return x + y
|
||||
}
|
||||
`,
|
||||
"52c56b52547c616516006c766b00527ac46203006c766b00c3616c756653c56b6c766b00527ac46c766b51527ac46203006c766b00c36c766b51c393616c7566",
|
||||
},
|
||||
}
|
||||
|
|
157
pkg/vm/compiler/tests/struct_test.go
Normal file
157
pkg/vm/compiler/tests/struct_test.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package compiler_test
|
||||
|
||||
var structTestCases = []testCase{
|
||||
{
|
||||
"struct field assign",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
t := token {
|
||||
x: 2,
|
||||
y: 4,
|
||||
}
|
||||
|
||||
age := t.x
|
||||
return age
|
||||
}
|
||||
`,
|
||||
"53c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac46c766b00c300c36c766b51527ac46203006c766b51c3616c7566",
|
||||
},
|
||||
{
|
||||
"struct field return",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
t := token {
|
||||
x: 2,
|
||||
y: 4,
|
||||
}
|
||||
|
||||
return t.x
|
||||
}
|
||||
`,
|
||||
"52c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac46203006c766b00c300c3616c7566",
|
||||
},
|
||||
{
|
||||
"struct field assign",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
t := token {
|
||||
x: 2,
|
||||
y: 4,
|
||||
}
|
||||
t.x = 10
|
||||
return t.x
|
||||
}
|
||||
`,
|
||||
"53c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac45a6c766b00c3007bc46203006c766b00c300c3616c7566",
|
||||
},
|
||||
{
|
||||
"complex struct",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
x := 10
|
||||
|
||||
t := token {
|
||||
x: 2,
|
||||
y: 4,
|
||||
}
|
||||
|
||||
y := x + t.x
|
||||
|
||||
return y
|
||||
}
|
||||
`,
|
||||
"54c56b5a6c766b00527ac46152c66b526c766b00527ac4546c766b51527ac46c6c766b51527ac46c766b00c36c766b51c300c3936c766b52527ac46203006c766b52c3616c7566",
|
||||
},
|
||||
{
|
||||
"initialize same struct twice",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
t1 := token {
|
||||
x: 2,
|
||||
y: 4,
|
||||
}
|
||||
t2 := token {
|
||||
x: 2,
|
||||
y: 4,
|
||||
}
|
||||
return t1.x + t2.y
|
||||
}
|
||||
`,
|
||||
"53c56b6152c66b526c766b00527ac4546c766b51527ac46c6c766b00527ac46152c66b526c766b00527ac4546c766b51527ac46c6c766b51527ac46203006c766b00c300c36c766b51c351c393616c7566",
|
||||
},
|
||||
{
|
||||
"struct methods",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
}
|
||||
|
||||
func(t token) getInteger() int {
|
||||
return t.x
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
t := token {
|
||||
x: 4,
|
||||
}
|
||||
someInt := t.getInteger()
|
||||
return someInt
|
||||
}
|
||||
`,
|
||||
"53c56b6151c66b546c766b00527ac46c6c766b00527ac46c766b00c3616516006c766b51527ac46203006c766b51c3616c756652c56b6c766b00527ac46203006c766b00c300c3616c7566",
|
||||
},
|
||||
{
|
||||
"struct methods with arguments",
|
||||
`
|
||||
package foo
|
||||
type token struct {
|
||||
x int
|
||||
}
|
||||
|
||||
// Also tests if x conflicts with t.x
|
||||
func(t token) addIntegers(x int, y int) int {
|
||||
return t.x + x + y
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
t := token {
|
||||
x: 4,
|
||||
}
|
||||
someInt := t.addIntegers(2, 4)
|
||||
return someInt
|
||||
}
|
||||
`,
|
||||
"53c56b6151c66b546c766b00527ac46c6c766b00527ac46c766b00c352545272616516006c766b51527ac46203006c766b51c3616c756654c56b6c766b00527ac46c766b51527ac46c766b52527ac46203006c766b00c300c36c766b51c3936c766b52c393616c7566",
|
||||
},
|
||||
}
|
214
pkg/vm/opcode.go
214
pkg/vm/opcode.go
|
@ -1,129 +1,129 @@
|
|||
package vm
|
||||
|
||||
// OpCode is an single operational instruction for the GO NEO virtual machine.
|
||||
type OpCode byte
|
||||
// Opcode is an single operational instruction for the GO NEO virtual machine.
|
||||
type Opcode byte
|
||||
|
||||
// List of supported opcodes.
|
||||
const (
|
||||
// Constants
|
||||
OpPush0 OpCode = 0x00 // An empty array of bytes is pushed onto the stack.
|
||||
OpPushF OpCode = OpPush0
|
||||
OpPushBytes1 OpCode = 0x01 // 0x01-0x4B The next opcode bytes is data to be pushed onto the stack
|
||||
OpPushBytes75 OpCode = 0x4B
|
||||
OpPushData1 OpCode = 0x4C // The next byte contains the number of bytes to be pushed onto the stack.
|
||||
OpPushData2 OpCode = 0x4D // The next two bytes contain the number of bytes to be pushed onto the stack.
|
||||
OpPushData4 OpCode = 0x4E // The next four bytes contain the number of bytes to be pushed onto the stack.
|
||||
OpPushM1 OpCode = 0x4F // The number -1 is pushed onto the stack.
|
||||
OpPush1 OpCode = 0x51
|
||||
OpPushT OpCode = OpPush1
|
||||
OpPush2 OpCode = 0x52 // The number 2 is pushed onto the stack.
|
||||
OpPush3 OpCode = 0x53 // The number 3 is pushed onto the stack.
|
||||
OpPush4 OpCode = 0x54 // The number 4 is pushed onto the stack.
|
||||
OpPush5 OpCode = 0x55 // The number 5 is pushed onto the stack.
|
||||
OpPush6 OpCode = 0x56 // The number 6 is pushed onto the stack.
|
||||
OpPush7 OpCode = 0x57 // The number 7 is pushed onto the stack.
|
||||
OpPush8 OpCode = 0x58 // The number 8 is pushed onto the stack.
|
||||
OpPush9 OpCode = 0x59 // The number 9 is pushed onto the stack.
|
||||
OpPush10 OpCode = 0x5A // The number 10 is pushed onto the stack.
|
||||
OpPush11 OpCode = 0x5B // The number 11 is pushed onto the stack.
|
||||
OpPush12 OpCode = 0x5C // The number 12 is pushed onto the stack.
|
||||
OpPush13 OpCode = 0x5D // The number 13 is pushed onto the stack.
|
||||
OpPush14 OpCode = 0x5E // The number 14 is pushed onto the stack.
|
||||
OpPush15 OpCode = 0x5F // The number 15 is pushed onto the stack.
|
||||
OpPush16 OpCode = 0x60 // The number 16 is pushed onto the stack.
|
||||
Opush0 Opcode = 0x00 // An empty array of bytes is pushed onto the stack.
|
||||
Opushf Opcode = Opush0
|
||||
Opushbytes1 Opcode = 0x01 // 0x01-0x4B The next opcode bytes is data to be pushed onto the stack
|
||||
Opushbytes75 Opcode = 0x4B
|
||||
Opushdata1 Opcode = 0x4C // The next byte contains the number of bytes to be pushed onto the stack.
|
||||
Opushdata2 Opcode = 0x4D // The next two bytes contain the number of bytes to be pushed onto the stack.
|
||||
Opushdata4 Opcode = 0x4E // The next four bytes contain the number of bytes to be pushed onto the stack.
|
||||
Opushm1 Opcode = 0x4F // The number -1 is pushed onto the stack.
|
||||
Opush1 Opcode = 0x51
|
||||
Opusht Opcode = Opush1
|
||||
Opush2 Opcode = 0x52 // The number 2 is pushed onto the stack.
|
||||
Opush3 Opcode = 0x53 // The number 3 is pushed onto the stack.
|
||||
Opush4 Opcode = 0x54 // The number 4 is pushed onto the stack.
|
||||
Opush5 Opcode = 0x55 // The number 5 is pushed onto the stack.
|
||||
Opush6 Opcode = 0x56 // The number 6 is pushed onto the stack.
|
||||
Opush7 Opcode = 0x57 // The number 7 is pushed onto the stack.
|
||||
Opush8 Opcode = 0x58 // The number 8 is pushed onto the stack.
|
||||
Opush9 Opcode = 0x59 // The number 9 is pushed onto the stack.
|
||||
Opush10 Opcode = 0x5A // The number 10 is pushed onto the stack.
|
||||
Opush11 Opcode = 0x5B // The number 11 is pushed onto the stack.
|
||||
Opush12 Opcode = 0x5C // The number 12 is pushed onto the stack.
|
||||
Opush13 Opcode = 0x5D // The number 13 is pushed onto the stack.
|
||||
Opush14 Opcode = 0x5E // The number 14 is pushed onto the stack.
|
||||
Opush15 Opcode = 0x5F // The number 15 is pushed onto the stack.
|
||||
Opush16 Opcode = 0x60 // The number 16 is pushed onto the stack.
|
||||
|
||||
// Flow control
|
||||
OpNOP OpCode = 0x61 // No operation.
|
||||
OpJMP OpCode = 0x62
|
||||
OpJMPIF OpCode = 0x63
|
||||
OpJMPIFNOT OpCode = 0x64
|
||||
OpCall OpCode = 0x65
|
||||
OpRET OpCode = 0x66
|
||||
OpAppCall OpCode = 0x67
|
||||
OpSysCall OpCode = 0x68
|
||||
OpTailCall OpCode = 0x69
|
||||
Onop Opcode = 0x61 // No operation.
|
||||
Ojmp Opcode = 0x62
|
||||
Ojmpif Opcode = 0x63
|
||||
Ojmpifnot Opcode = 0x64
|
||||
Ocall Opcode = 0x65
|
||||
Oret Opcode = 0x66
|
||||
Opcall Opcode = 0x67
|
||||
Osyscall Opcode = 0x68
|
||||
Otailcall Opcode = 0x69
|
||||
|
||||
// The stack
|
||||
OpDupFromAltStack OpCode = 0x6A
|
||||
OpToAltStack OpCode = 0x6B // Puts the input onto the top of the alt stack. Removes it from the main stack.
|
||||
OpFromAltStack OpCode = 0x6C // Puts the input onto the top of the main stack. Removes it from the alt stack.
|
||||
OpXDrop OpCode = 0x6D
|
||||
OpXSwap OpCode = 0x72
|
||||
OpXTuck OpCode = 0x73
|
||||
OpDepth OpCode = 0x74 // Puts the number of stack items onto the stack.
|
||||
OpDrop OpCode = 0x75 // Removes the top stack item.
|
||||
OpDup OpCode = 0x76 // Duplicates the top stack item.
|
||||
OpNip OpCode = 0x77 // Removes the second-to-top stack item.
|
||||
OpOver OpCode = 0x78 // Copies the second-to-top stack item to the top.
|
||||
OpPick OpCode = 0x79 // The item n back in the stack is copied to the top.
|
||||
OpRoll OpCode = 0x7A // The item n back in the stack is moved to the top.
|
||||
OpRot OpCode = 0x7B // The top three items on the stack are rotated to the left.
|
||||
OpSwap OpCode = 0x7C // The top two items on the stack are swapped.
|
||||
OpTuck OpCode = 0x7D // The item at the top of the stack is copied and inserted before the second-to-top item.
|
||||
Odupfromaltstack Opcode = 0x6A
|
||||
Otoaltstack Opcode = 0x6B // Puts the input onto the top of the alt stack. Removes it from the main stack.
|
||||
Ofromaltstack Opcode = 0x6C // Puts the input onto the top of the main stack. Removes it from the alt stack.
|
||||
Oxdrop Opcode = 0x6D
|
||||
Oxswap Opcode = 0x72
|
||||
Oxtuck Opcode = 0x73
|
||||
Odepth Opcode = 0x74 // Puts the number of stack items onto the stack.
|
||||
Odrop Opcode = 0x75 // Removes the top stack item.
|
||||
Odup Opcode = 0x76 // Duplicates the top stack item.
|
||||
Onip Opcode = 0x77 // Removes the second-to-top stack item.
|
||||
Oover Opcode = 0x78 // Copies the second-to-top stack item to the top.
|
||||
Opick Opcode = 0x79 // The item n back in the stack is copied to the top.
|
||||
Oroll Opcode = 0x7A // The item n back in the stack is moved to the top.
|
||||
Orot Opcode = 0x7B // The top three items on the stack are rotated to the left.
|
||||
Oswap Opcode = 0x7C // The top two items on the stack are swapped.
|
||||
Otuck Opcode = 0x7D // The item at the top of the stack is copied and inserted before the second-to-top item.
|
||||
|
||||
// Splice
|
||||
OpCat OpCode = 0x7E // Concatenates two strings.
|
||||
OpSubStr OpCode = 0x7F // Returns a section of a string.
|
||||
OpLeft OpCode = 0x80 // Keeps only characters left of the specified point in a string.
|
||||
OpRight OpCode = 0x81 // Keeps only characters right of the specified point in a string.
|
||||
OpSize OpCode = 0x82 // Returns the length of the input string.
|
||||
Ocat Opcode = 0x7E // Concatenates two strings.
|
||||
Osubstr Opcode = 0x7F // Returns a section of a string.
|
||||
Oleft Opcode = 0x80 // Keeps only characters left of the specified point in a string.
|
||||
Oright Opcode = 0x81 // Keeps only characters right of the specified point in a string.
|
||||
Osize Opcode = 0x82 // Returns the length of the input string.
|
||||
|
||||
// Bitwise logic
|
||||
OpInvert OpCode = 0x83 // Flips all of the bits in the input.
|
||||
OpAnd OpCode = 0x84 // Boolean and between each bit in the inputs.
|
||||
OpOr OpCode = 0x85 // Boolean or between each bit in the inputs.
|
||||
OpXor OpCode = 0x86 // Boolean exclusive or between each bit in the inputs.
|
||||
OpEqual OpCode = 0x87 // Returns 1 if the inputs are exactly equal, 0 otherwise.
|
||||
Oinvert Opcode = 0x83 // Flips all of the bits in the input.
|
||||
Oand Opcode = 0x84 // Boolean and between each bit in the inputs.
|
||||
Oor Opcode = 0x85 // Boolean or between each bit in the inputs.
|
||||
Oxor Opcode = 0x86 // Boolean exclusive or between each bit in the inputs.
|
||||
Oequal Opcode = 0x87 // Returns 1 if the inputs are exactly equal, 0 otherwise.
|
||||
|
||||
// Arithmetic
|
||||
// Note: Arithmetic inputs are limited to signed 32-bit integers, but may overflow their output.
|
||||
OpInc OpCode = 0x8B // 1 is added to the input.
|
||||
OpDec OpCode = 0x8C // 1 is subtracted from the input.
|
||||
OpSign OpCode = 0x8D
|
||||
OpNegate OpCode = 0x8F // The sign of the input is flipped.
|
||||
OpAbs OpCode = 0x90 // The input is made positive.
|
||||
OpNot OpCode = 0x91 // If the input is 0 or 1, it is flipped. Otherwise the output will be 0.
|
||||
OpNZ OpCode = 0x92 // Returns 0 if the input is 0. 1 otherwise.
|
||||
OpAdd OpCode = 0x93 // a is added to b.
|
||||
OpSub OpCode = 0x94 // b is subtracted from a.
|
||||
OpMul OpCode = 0x95 // a is multiplied by b.
|
||||
OpDiv OpCode = 0x96 // a is divided by b.
|
||||
OpMod OpCode = 0x97 // Returns the remainder after dividing a by b.
|
||||
OpShl OpCode = 0x98 // Shifts a left b bits, preserving sign.
|
||||
OpShr OpCode = 0x99 // Shifts a right b bits, preserving sign.
|
||||
OpBoolAnd OpCode = 0x9A // If both a and b are not 0, the output is 1. Otherwise 0.
|
||||
OpBoolOr OpCode = 0x9B // If a or b is not 0, the output is 1. Otherwise 0.
|
||||
OpNumEqual OpCode = 0x9C // Returns 1 if the numbers are equal, 0 otherwise.
|
||||
OpNumNotEqual OpCode = 0x9E // Returns 1 if the numbers are not equal, 0 otherwise.
|
||||
OpLT OpCode = 0x9F // Returns 1 if a is less than b, 0 otherwise.
|
||||
OpGT OpCode = 0xA0 // Returns 1 if a is greater than b, 0 otherwise.
|
||||
OpLTE OpCode = 0xA1 // Returns 1 if a is less than or equal to b, 0 otherwise.
|
||||
OpGTE OpCode = 0xA2 // Returns 1 if a is greater than or equal to b, 0 otherwise.
|
||||
OpMin OpCode = 0xA3 // Returns the smaller of a and b.
|
||||
OpMax OpCode = 0xA4 // Returns the larger of a and b.
|
||||
OpWithin OpCode = 0xA5 // Returns 1 if x is within the specified range (left-inclusive), 0 otherwise.
|
||||
Oinc Opcode = 0x8B // 1 is added to the input.
|
||||
Odec Opcode = 0x8C // 1 is subtracted from the input.
|
||||
Osign Opcode = 0x8D
|
||||
Onegate Opcode = 0x8F // The sign of the input is flipped.
|
||||
Oabs Opcode = 0x90 // The input is made positive.
|
||||
Onot Opcode = 0x91 // If the input is 0 or 1, it is flipped. Otherwise the output will be 0.
|
||||
Onz Opcode = 0x92 // Returns 0 if the input is 0. 1 otherwise.
|
||||
Oadd Opcode = 0x93 // a is added to b.
|
||||
Osub Opcode = 0x94 // b is subtracted from a.
|
||||
Omul Opcode = 0x95 // a is multiplied by b.
|
||||
Odiv Opcode = 0x96 // a is divided by b.
|
||||
Omod Opcode = 0x97 // Returns the remainder after dividing a by b.
|
||||
Oshl Opcode = 0x98 // Shifts a left b bits, preserving sign.
|
||||
Oshr Opcode = 0x99 // Shifts a right b bits, preserving sign.
|
||||
Obooland Opcode = 0x9A // If both a and b are not 0, the output is 1. Otherwise 0.
|
||||
Oboolor Opcode = 0x9B // If a or b is not 0, the output is 1. Otherwise 0.
|
||||
Onumequal Opcode = 0x9C // Returns 1 if the numbers are equal, 0 otherwise.
|
||||
Onumnotequal Opcode = 0x9E // Returns 1 if the numbers are not equal, 0 otherwise.
|
||||
Olt Opcode = 0x9F // Returns 1 if a is less than b, 0 otherwise.
|
||||
Ogt Opcode = 0xA0 // Returns 1 if a is greater than b, 0 otherwise.
|
||||
Olte Opcode = 0xA1 // Returns 1 if a is less than or equal to b, 0 otherwise.
|
||||
Ogte Opcode = 0xA2 // Returns 1 if a is greater than or equal to b, 0 otherwise.
|
||||
Omin Opcode = 0xA3 // Returns the smaller of a and b.
|
||||
Omax Opcode = 0xA4 // Returns the larger of a and b.
|
||||
Owithin Opcode = 0xA5 // Returns 1 if x is within the specified range (left-inclusive), 0 otherwise.
|
||||
|
||||
// Crypto
|
||||
OpSHA1 OpCode = 0xA7 // The input is hashed using SHA-1.
|
||||
OpSHA256 OpCode = 0xA8 // The input is hashed using SHA-256.
|
||||
OpHASH160 OpCode = 0xA9
|
||||
OpHASH256 OpCode = 0xAA
|
||||
OpCheckSig OpCode = 0xAC
|
||||
OpCheckMultiSig OpCode = 0xAE
|
||||
Osha1 Opcode = 0xA7 // The input is hashed using SHA-1.
|
||||
Osha256 Opcode = 0xA8 // The input is hashed using SHA-256.
|
||||
Ohash160 Opcode = 0xA9
|
||||
Ohash256 Opcode = 0xAA
|
||||
Ochecksig Opcode = 0xAC
|
||||
Ocheckmultisig Opcode = 0xAE
|
||||
|
||||
// Array
|
||||
OpArraySize OpCode = 0xC0
|
||||
OpPack OpCode = 0xC1
|
||||
OpUnpack OpCode = 0xC2
|
||||
OpPickItem OpCode = 0xC3
|
||||
OpSetItem OpCode = 0xC4
|
||||
OpNewArray OpCode = 0xC5 // Pops size from stack and creates a new array with that size, and pushes the array into the stack
|
||||
OpNewStruct OpCode = 0xC6
|
||||
OpAppend OpCode = 0xC8
|
||||
OpReverse OpCode = 0xC9
|
||||
OpRemove OpCode = 0xCA
|
||||
// array
|
||||
Oarraysize Opcode = 0xC0
|
||||
Opack Opcode = 0xC1
|
||||
Ounpack Opcode = 0xC2
|
||||
Opickitem Opcode = 0xC3
|
||||
Osetitem Opcode = 0xC4
|
||||
Onewarray Opcode = 0xC5 // Pops size from stack and creates a new array with that size, and pushes the array into the stack
|
||||
Onewstruct Opcode = 0xC6
|
||||
Oappend Opcode = 0xC8
|
||||
Oreverse Opcode = 0xC9
|
||||
Oremove Opcode = 0xCA
|
||||
|
||||
// Exceptions
|
||||
OpThrow OpCode = 0xF0
|
||||
OpThrowIfNot OpCode = 0xF1
|
||||
// exceptions
|
||||
Othrow Opcode = 0xF0
|
||||
Othrowifnot Opcode = 0xF1
|
||||
)
|
||||
|
|
|
@ -1,118 +1,118 @@
|
|||
// Code generated by "stringer -type=OpCode ./pkg/vm"; DO NOT EDIT.
|
||||
// Code generated by "stringer -type=Opcode ./pkg/vm/compiler"; DO NOT EDIT.
|
||||
|
||||
package vm
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _OpCode_name = "OpPush0OpPushBytes1OpPushBytes75OpPushData1OpPushData2OpPushData4OpPushM1OpPush1OpPush2OpPush3OpPush4OpPush5OpPush6OpPush7OpPush8OpPush9OpPush10OpPush11OpPush12OpPush13OpPush14OpPush15OpPush16OpNOPOpJMPOpJMPIFOpJMPIFNOTOpCallOpRETOpAppCallOpSysCallOpTailCallOpDupFromAltStackOpToAltStackOpFromAltStackOpXDropOpXSwapOpXTuckOpDepthOpDropOpDupOpNipOpOverOpPickOpRollOpRotOpSwapOpTuckOpCatOpSubStrOpLeftOpRightOpSizeOpInvertOpAndOpOrOpXorOpEqualOpIncOpDecOpSignOpNegateOpAbsOpNotOpNZOpAddOpSubOpMulOpDivOpModOpShlOpShrOpBoolAndOpBoolOrOpNumEqualOpNumNotEqualOpLTOpGTOpLTEOpGTEOpMinOpMaxOpWithinOpSHA1OpSHA256OpHASH160OpHASH256OpCheckSigOpCheckMultiSigOpArraySizeOpPackOpUnpackOpPickItemOpSetItemOpNewArrayOpNewStructOpAppendOpReverseOpRemoveOpThrowOpThrowIfNot"
|
||||
const _Opcode_name = "Opush0Opushbytes1Opushbytes75Opushdata1Opushdata2Opushdata4Opushm1Opush1Opush2Opush3Opush4Opush5Opush6Opush7Opush8Opush9Opush10Opush11Opush12Opush13Opush14Opush15Opush16OnopOjmpOjmpifOjmpifnotOcallOretOpcallOsyscallOtailcallOdupfromaltstackOtoaltstackOfromaltstackOxdropOxswapOxtuckOdepthOdropOdupOnipOoverOpickOrollOrotOswapOtuckOcatOsubstrOleftOrightOsizeOinvertOandOorOxorOequalOincOdecOsignOnegateOabsOnotOnzOaddOsubOmulOdivOmodOshlOshrOboolandOboolorOnumequalOnumnotequalOltOgtOlteOgteOminOmaxOwithinOsha1Osha256Ohash160Ohash256OchecksigOcheckmultisigOarraysizeOpackOunpackOpickitemOsetitemOnewarrayOnewstructOappendOreverseOremoveOthrowOthrowifnot"
|
||||
|
||||
var _OpCode_map = map[OpCode]string{
|
||||
0: _OpCode_name[0:7],
|
||||
1: _OpCode_name[7:19],
|
||||
75: _OpCode_name[19:32],
|
||||
76: _OpCode_name[32:43],
|
||||
77: _OpCode_name[43:54],
|
||||
78: _OpCode_name[54:65],
|
||||
79: _OpCode_name[65:73],
|
||||
81: _OpCode_name[73:80],
|
||||
82: _OpCode_name[80:87],
|
||||
83: _OpCode_name[87:94],
|
||||
84: _OpCode_name[94:101],
|
||||
85: _OpCode_name[101:108],
|
||||
86: _OpCode_name[108:115],
|
||||
87: _OpCode_name[115:122],
|
||||
88: _OpCode_name[122:129],
|
||||
89: _OpCode_name[129:136],
|
||||
90: _OpCode_name[136:144],
|
||||
91: _OpCode_name[144:152],
|
||||
92: _OpCode_name[152:160],
|
||||
93: _OpCode_name[160:168],
|
||||
94: _OpCode_name[168:176],
|
||||
95: _OpCode_name[176:184],
|
||||
96: _OpCode_name[184:192],
|
||||
97: _OpCode_name[192:197],
|
||||
98: _OpCode_name[197:202],
|
||||
99: _OpCode_name[202:209],
|
||||
100: _OpCode_name[209:219],
|
||||
101: _OpCode_name[219:225],
|
||||
102: _OpCode_name[225:230],
|
||||
103: _OpCode_name[230:239],
|
||||
104: _OpCode_name[239:248],
|
||||
105: _OpCode_name[248:258],
|
||||
106: _OpCode_name[258:275],
|
||||
107: _OpCode_name[275:287],
|
||||
108: _OpCode_name[287:301],
|
||||
109: _OpCode_name[301:308],
|
||||
114: _OpCode_name[308:315],
|
||||
115: _OpCode_name[315:322],
|
||||
116: _OpCode_name[322:329],
|
||||
117: _OpCode_name[329:335],
|
||||
118: _OpCode_name[335:340],
|
||||
119: _OpCode_name[340:345],
|
||||
120: _OpCode_name[345:351],
|
||||
121: _OpCode_name[351:357],
|
||||
122: _OpCode_name[357:363],
|
||||
123: _OpCode_name[363:368],
|
||||
124: _OpCode_name[368:374],
|
||||
125: _OpCode_name[374:380],
|
||||
126: _OpCode_name[380:385],
|
||||
127: _OpCode_name[385:393],
|
||||
128: _OpCode_name[393:399],
|
||||
129: _OpCode_name[399:406],
|
||||
130: _OpCode_name[406:412],
|
||||
131: _OpCode_name[412:420],
|
||||
132: _OpCode_name[420:425],
|
||||
133: _OpCode_name[425:429],
|
||||
134: _OpCode_name[429:434],
|
||||
135: _OpCode_name[434:441],
|
||||
139: _OpCode_name[441:446],
|
||||
140: _OpCode_name[446:451],
|
||||
141: _OpCode_name[451:457],
|
||||
143: _OpCode_name[457:465],
|
||||
144: _OpCode_name[465:470],
|
||||
145: _OpCode_name[470:475],
|
||||
146: _OpCode_name[475:479],
|
||||
147: _OpCode_name[479:484],
|
||||
148: _OpCode_name[484:489],
|
||||
149: _OpCode_name[489:494],
|
||||
150: _OpCode_name[494:499],
|
||||
151: _OpCode_name[499:504],
|
||||
152: _OpCode_name[504:509],
|
||||
153: _OpCode_name[509:514],
|
||||
154: _OpCode_name[514:523],
|
||||
155: _OpCode_name[523:531],
|
||||
156: _OpCode_name[531:541],
|
||||
158: _OpCode_name[541:554],
|
||||
159: _OpCode_name[554:558],
|
||||
160: _OpCode_name[558:562],
|
||||
161: _OpCode_name[562:567],
|
||||
162: _OpCode_name[567:572],
|
||||
163: _OpCode_name[572:577],
|
||||
164: _OpCode_name[577:582],
|
||||
165: _OpCode_name[582:590],
|
||||
167: _OpCode_name[590:596],
|
||||
168: _OpCode_name[596:604],
|
||||
169: _OpCode_name[604:613],
|
||||
170: _OpCode_name[613:622],
|
||||
172: _OpCode_name[622:632],
|
||||
174: _OpCode_name[632:647],
|
||||
192: _OpCode_name[647:658],
|
||||
193: _OpCode_name[658:664],
|
||||
194: _OpCode_name[664:672],
|
||||
195: _OpCode_name[672:682],
|
||||
196: _OpCode_name[682:691],
|
||||
197: _OpCode_name[691:701],
|
||||
198: _OpCode_name[701:712],
|
||||
200: _OpCode_name[712:720],
|
||||
201: _OpCode_name[720:729],
|
||||
202: _OpCode_name[729:737],
|
||||
240: _OpCode_name[737:744],
|
||||
241: _OpCode_name[744:756],
|
||||
var _Opcode_map = map[Opcode]string{
|
||||
0: _Opcode_name[0:6],
|
||||
1: _Opcode_name[6:17],
|
||||
75: _Opcode_name[17:29],
|
||||
76: _Opcode_name[29:39],
|
||||
77: _Opcode_name[39:49],
|
||||
78: _Opcode_name[49:59],
|
||||
79: _Opcode_name[59:66],
|
||||
81: _Opcode_name[66:72],
|
||||
82: _Opcode_name[72:78],
|
||||
83: _Opcode_name[78:84],
|
||||
84: _Opcode_name[84:90],
|
||||
85: _Opcode_name[90:96],
|
||||
86: _Opcode_name[96:102],
|
||||
87: _Opcode_name[102:108],
|
||||
88: _Opcode_name[108:114],
|
||||
89: _Opcode_name[114:120],
|
||||
90: _Opcode_name[120:127],
|
||||
91: _Opcode_name[127:134],
|
||||
92: _Opcode_name[134:141],
|
||||
93: _Opcode_name[141:148],
|
||||
94: _Opcode_name[148:155],
|
||||
95: _Opcode_name[155:162],
|
||||
96: _Opcode_name[162:169],
|
||||
97: _Opcode_name[169:173],
|
||||
98: _Opcode_name[173:177],
|
||||
99: _Opcode_name[177:183],
|
||||
100: _Opcode_name[183:192],
|
||||
101: _Opcode_name[192:197],
|
||||
102: _Opcode_name[197:201],
|
||||
103: _Opcode_name[201:207],
|
||||
104: _Opcode_name[207:215],
|
||||
105: _Opcode_name[215:224],
|
||||
106: _Opcode_name[224:240],
|
||||
107: _Opcode_name[240:251],
|
||||
108: _Opcode_name[251:264],
|
||||
109: _Opcode_name[264:270],
|
||||
114: _Opcode_name[270:276],
|
||||
115: _Opcode_name[276:282],
|
||||
116: _Opcode_name[282:288],
|
||||
117: _Opcode_name[288:293],
|
||||
118: _Opcode_name[293:297],
|
||||
119: _Opcode_name[297:301],
|
||||
120: _Opcode_name[301:306],
|
||||
121: _Opcode_name[306:311],
|
||||
122: _Opcode_name[311:316],
|
||||
123: _Opcode_name[316:320],
|
||||
124: _Opcode_name[320:325],
|
||||
125: _Opcode_name[325:330],
|
||||
126: _Opcode_name[330:334],
|
||||
127: _Opcode_name[334:341],
|
||||
128: _Opcode_name[341:346],
|
||||
129: _Opcode_name[346:352],
|
||||
130: _Opcode_name[352:357],
|
||||
131: _Opcode_name[357:364],
|
||||
132: _Opcode_name[364:368],
|
||||
133: _Opcode_name[368:371],
|
||||
134: _Opcode_name[371:375],
|
||||
135: _Opcode_name[375:381],
|
||||
139: _Opcode_name[381:385],
|
||||
140: _Opcode_name[385:389],
|
||||
141: _Opcode_name[389:394],
|
||||
143: _Opcode_name[394:401],
|
||||
144: _Opcode_name[401:405],
|
||||
145: _Opcode_name[405:409],
|
||||
146: _Opcode_name[409:412],
|
||||
147: _Opcode_name[412:416],
|
||||
148: _Opcode_name[416:420],
|
||||
149: _Opcode_name[420:424],
|
||||
150: _Opcode_name[424:428],
|
||||
151: _Opcode_name[428:432],
|
||||
152: _Opcode_name[432:436],
|
||||
153: _Opcode_name[436:440],
|
||||
154: _Opcode_name[440:448],
|
||||
155: _Opcode_name[448:455],
|
||||
156: _Opcode_name[455:464],
|
||||
158: _Opcode_name[464:476],
|
||||
159: _Opcode_name[476:479],
|
||||
160: _Opcode_name[479:482],
|
||||
161: _Opcode_name[482:486],
|
||||
162: _Opcode_name[486:490],
|
||||
163: _Opcode_name[490:494],
|
||||
164: _Opcode_name[494:498],
|
||||
165: _Opcode_name[498:505],
|
||||
167: _Opcode_name[505:510],
|
||||
168: _Opcode_name[510:517],
|
||||
169: _Opcode_name[517:525],
|
||||
170: _Opcode_name[525:533],
|
||||
172: _Opcode_name[533:542],
|
||||
174: _Opcode_name[542:556],
|
||||
192: _Opcode_name[556:566],
|
||||
193: _Opcode_name[566:571],
|
||||
194: _Opcode_name[571:578],
|
||||
195: _Opcode_name[578:587],
|
||||
196: _Opcode_name[587:595],
|
||||
197: _Opcode_name[595:604],
|
||||
198: _Opcode_name[604:614],
|
||||
200: _Opcode_name[614:621],
|
||||
201: _Opcode_name[621:629],
|
||||
202: _Opcode_name[629:636],
|
||||
240: _Opcode_name[636:642],
|
||||
241: _Opcode_name[642:653],
|
||||
}
|
||||
|
||||
func (i OpCode) String() string {
|
||||
if str, ok := _OpCode_map[i]; ok {
|
||||
func (i Opcode) String() string {
|
||||
if str, ok := _Opcode_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "OpCode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
return "Opcode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue