neoneo-go/pkg/vm/compiler/compiler.go
Anthony De Meulemeester 8fe079ec8e
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
2018-02-19 10:24:28 +01:00

118 lines
2.5 KiB
Go

package compiler
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"io"
"io/ioutil"
"log"
"os"
"strings"
"text/tabwriter"
"github.com/CityOfZion/neo-go/pkg/vm"
)
const fileExt = ".avm"
// 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
}
// 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, "", input, 0)
if err != nil {
return nil, err
}
conf := types.Config{Importer: importer.Default()}
typeInfo := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Scopes: make(map[ast.Node]*types.Scope),
}
// Typechecker
_, err = conf.Check("", fset, []*ast.File{f}, typeInfo)
if err != nil {
return nil, err
}
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")
}
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))
}
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
}
func init() {
log.SetFlags(0)
}