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:
Anthony De Meulemeester 2018-02-19 10:24:28 +01:00 committed by GitHub
parent b257a06f3e
commit 8fe079ec8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1298 additions and 1064 deletions

View file

@ -1 +1 @@
0.10.0
0.11.0

View file

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

View file

@ -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
}
buf, err := CodeGen(f, typeInfo)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// 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")
}
return true
})
if main == nil {
log.Fatal("could not find func main. did you forgot to declare it?")
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))
}
c.resolveFuncDecls(f)
c.convertFuncDecl(main)
out := fmt.Sprintf("%s.%s", o.Outfile, o.Ext)
return ioutil.WriteFile(out, b, os.ModePerm)
}
// 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)
}
}
// 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
}
// update all local function calls.
c.updateFuncCalls()
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
View 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
}

View file

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

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

View file

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

View file

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

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

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

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

View file

@ -0,0 +1,15 @@
package compiler_test
var boolTestCases = []testCase{
{
"bool assign",
`
package foo
func Main() bool {
x := true
return x
}
`,
"52c56b516c766b00527ac46203006c766b00c3616c7566",
},
}

View file

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

View file

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

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

View file

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

View file

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