func (e *env) parseASTStructType(name string, typ *ast.StructType) (*Value, error) {
	v := &Value{
		name: name,
		t:    TypeContainer,
		o:    []*Value{},
	}
	for _, f := range typ.Fields.List {
		if len(f.Names) != 1 {
			continue
		}
		name := f.Names[0].Name
		if !isExportedField(name) {
			continue
		}
		if strings.HasPrefix(name, "XXX_") {
			continue
		}
		var tags string
		if f.Tag != nil {
			tags = f.Tag.Value
		}
		elem, err := e.parseASTFieldType(name, tags, f.Type)
		if err != nil {
			return nil, err
		}
		if elem == nil {
			continue
		}
		elem.name = name
		v.o = append(v.o, elem)
	}
	for _, f := range v.o {
		if f.isFixed() {
			v.n += f.n
		} else {
			v.n += bytesPerLengthOffset
			v.c = true
		}
	}
	return v, nil
}
parse the Go AST struct
package main
import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"flag"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"text/template"
)
const bytesPerLengthOffset = 4
func main() {
	var source string
	var objsStr string
	var output string
	var include string
	var experimental bool
	flag.StringVar(&source, "path", "", "")
	flag.StringVar(&objsStr, "objs", "", "")
	flag.StringVar(&output, "output", "", "")
	flag.StringVar(&include, "include", "", "")
	flag.BoolVar(&experimental, "experimental", false, "")
	flag.Parse()
	targets := decodeList(objsStr)
	includeList := decodeList(include)
	if err := encode(source, targets, output, includeList, experimental); err != nil {
		fmt.Printf("[ERR]: %v\n", err)
		os.Exit(1)
	}
}
func decodeList(input string) []string {
	if input == "" {
		return []string{}
	}
	return strings.Split(strings.TrimSpace(input), ",")
}
func encode(source string, targets []string, output string, includePaths []string, experimental bool) error {
	files, err := parseInput(source)  
	if err != nil {
		return err
	}
	include := map[string]*ast.File{}
	for _, i := range includePaths {
		files, err := parseInput(i)
		if err != nil {
			return err
		}
		for k, v := range files {
			include[k] = v
		}
	}
	var packName string
	for _, file := range files {
		packName = file.Name.Name
	}
	e := &env{
		include:  include,
		source:   source,
		files:    files,
		objs:     map[string]*Value{},
		packName: packName,
		targets:  targets,
	}
	if err := e.generateIR(); err != nil {  
		return err
	}
	var out map[string]string
	if output == "" {
		out, err = e.generateEncodings(experimental)
	} else {
		out, err = e.generateOutputEncodings(output, experimental)
	}
	if err != nil {
		panic(err)
	}
	if out == nil {
		panic("No files to generate")
	}
	for name, str := range out {
		output := []byte(str)
		output, err = format.Source(output)
		if err != nil {
			return err
		}
		if err := ioutil.WriteFile(name, output, 0644); err != nil {
			return err
		}
	}
	return nil
}
func isDir(path string) (bool, error) {
	fileInfo, err := os.Stat(path)
	if err != nil {
		return false, err
	}
	return fileInfo.IsDir(), nil
}
func parseInput(source string) (map[string]*ast.File, error) {
	files := map[string]*ast.File{}
	ok, err := isDir(source)
	if err != nil {
		return nil, err
	}
	if ok {
		astFiles, err := parser.ParseDir(token.NewFileSet(), source, nil, parser.AllErrors)
		if err != nil {
			return nil, err
		}
		for _, v := range astFiles {
			if !strings.HasSuffix(v.Name, "_test") {
				files = v.Files
			}
		}
	} else {
		astfile, err := parser.ParseFile(token.NewFileSet(), source, nil, parser.AllErrors)
		if err != nil {
			return nil, err
		}
		files[source] = astfile
	}
	return files, nil
}
type Value struct {
	name string
	obj string
	n uint64
	s uint64
	t Type
	o []*Value
	e *Value
	c bool
	m uint64
	ref string
	noPtr bool
}
func (v *Value) isListElem() bool {
	return strings.HasSuffix(v.name, "]")
}
func (v *Value) objRef() string {
	if v.ref == "" {
		return v.obj
	}
	return v.ref + "." + v.obj
}
func (v *Value) copy() *Value {
	vv := new(Value)
	*vv = *v
	vv.o = make([]*Value, len(v.o))
	for indx := range v.o {
		vv.o[indx] = v.o[indx].copy()
	}
	if v.e != nil {
		vv.e = v.e.copy()
	}
	return vv
}
type Type int
const (
	TypeUint Type = iota
	TypeBool
	TypeBytes
	TypeBitVector
	TypeBitList
	TypeVector
	TypeList
	TypeContainer
	TypeReference
)
func (t Type) String() string {
	switch t {
	case TypeUint:
		return "uint"
	case TypeBool:
		return "bool"
	case TypeBytes:
		return "bytes"
	case TypeBitVector:
		return "bitvector"
	case TypeBitList:
		return "bitlist"
	case TypeVector:
		return "vector"
	case TypeList:
		return "list"
	case TypeContainer:
		return "container"
	case TypeReference:
		return "reference"
	default:
		panic("not found")
	}
}
type env struct {
	source string
	include map[string]*ast.File
	files map[string]*ast.File
	packName string
	raw []*astStruct
	objs map[string]*Value
	order map[string][]string
	targets []string
	imports []*astImport
}
const encodingPrefix = "_encoding.go"
func (e *env) generateOutputEncodings(output string, experimental bool) (map[string]string, error) {
	out := map[string]string{}
	keys := make([]string, 0, len(e.order))
	for k := range e.order {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	orders := []string{}
	for _, k := range keys {
		orders = append(orders, e.order[k]...)
	}
	res, ok, err := e.print(true, orders, experimental)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, nil
	}
	out[output] = res
	return out, nil
}
func (e *env) generateEncodings(experimental bool) (map[string]string, error) {
	outs := map[string]string{}
	firstDone := true
	for name, order := range e.order {
		ext := filepath.Ext(name)
		name = strings.TrimSuffix(name, ext)
		name += encodingPrefix
		vvv, ok, err := e.print(firstDone, order, experimental)
		if err != nil {
			return nil, err
		}
		if ok {
			firstDone = false
			outs[name] = vvv
		}
	}
	return outs, nil
}
func (e *env) hashSource() (string, error) {
	content := ""
	for _, f := range e.files {
		var buf bytes.Buffer
		if err := format.Node(&buf, token.NewFileSet(), f); err != nil {
			return "", err
		}
		content += buf.String()
	}
	hash := sha256.Sum256([]byte(content))
	return hex.EncodeToString(hash[:]), nil
}
func (e *env) print(first bool, order []string, experimental bool) (string, bool, error) {
	hash, err := e.hashSource()
	if err != nil {
		return "", false, fmt.Errorf("failed to hash files: %v", err)
	}
	tmpl := ` 
	package {{.package}}
	import (
		ssz "github.com/ferranbt/fastssz" {{ if .imports }}{{ range $value := .imports }}
			{{ $value }} {{ end }}
		{{ end }}
	)
	{{ range .objs }}
		{{ .Marshal }}
		{{ .Unmarshal }}
		{{ .Size }}
		{{ .HashTreeRoot }}
		{{ .GetTree }}
	{{ end }}
	`
	data := map[string]interface{}{
		"package": e.packName,
		"hash":    hash,
	}
	type Obj struct {
		Size, Marshal, Unmarshal, HashTreeRoot, GetTree string
	}
	objs := []*Obj{}
	imports := []string{}
	for _, name := range order {
		obj, ok := e.objs[name]
		if !ok {
			continue
		}
		refs := detectImports(obj)
		imports = appendWithoutRepeated(imports, refs)
		if obj.isFixed() && isBasicType(obj) {
			continue
		}
		getTree := ""
		if experimental {
			getTree = e.getTree(name, obj)
		}
		objs = append(objs, &Obj{
			HashTreeRoot: e.hashTreeRoot(name, obj),
			GetTree:      getTree,
			Marshal:      e.marshal(name, obj),
			Unmarshal:    e.unmarshal(name, obj),
			Size:         e.size(name, obj),
		})
	}
	if len(objs) == 0 {
		return "", false, nil
	}
	data["objs"] = objs
	importsStr, err := e.buildImports(imports)
	if err != nil {
		return "", false, err
	}
	if len(importsStr) != 0 {
		data["imports"] = importsStr
	}
	return execTmpl(tmpl, data), true, nil
}
func isBasicType(v *Value) bool {
	return v.t == TypeUint || v.t == TypeBool || v.t == TypeBytes
}
func (e *env) buildImports(imports []string) ([]string, error) {
	res := []string{}
	for _, i := range imports {
		imp := e.findImport(i)
		if imp != "" {
			res = append(res, imp)
		}
	}
	return res, nil
}
func (e *env) findImport(name string) string {
	for _, i := range e.imports {
		if i.match(name) {
			return i.getFullName()
		}
	}
	return ""
}
func appendWithoutRepeated(s []string, i []string) []string {
	for _, j := range i {
		if !contains(j, s) {
			s = append(s, j)
		}
	}
	return s
}
func detectImports(v *Value) []string {
	refs := []string{}
	for _, i := range v.o {
		var ref string
		switch i.t {
		case TypeReference:
			if !i.noPtr {
				ref = i.ref
			}
		case TypeContainer:
			ref = i.ref
		case TypeList, TypeVector:
			ref = i.e.ref
		default:
			ref = i.ref
		}
		if ref != "" {
			refs = append(refs, ref)
		}
	}
	return refs
}
func appendObjSignature(str string, v *Value) string {
	sig := strings.ToLower(string(v.name[0]))
	return strings.Replace(str, "::", sig, -1)
}
type astStruct struct {
	name     string
	obj      *ast.StructType
	packName string
	typ      ast.Expr
	implFunc bool
	isRef    bool
}
type astResult struct {
	objs     []*astStruct
	funcs    []string
	packName string
}
func decodeASTStruct(file *ast.File) *astResult {
	packName := file.Name.String()
	res := &astResult{
		objs:     []*astStruct{},
		funcs:    []string{},
		packName: packName,
	}
	funcRefs := map[string]int{}
	for _, dec := range file.Decls {
		if genDecl, ok := dec.(*ast.GenDecl); ok {
			for _, spec := range genDecl.Specs {
				if typeSpec, ok := spec.(*ast.TypeSpec); ok {
					obj := &astStruct{
						name:     typeSpec.Name.Name,
						packName: packName,
					}
					structType, ok := typeSpec.Type.(*ast.StructType)
					if ok {
						obj.obj = structType
					} else {
						if _, ok := typeSpec.Type.(*ast.InterfaceType); !ok {
							obj.typ = typeSpec.Type
						}
					}
					if obj.obj != nil || obj.typ != nil {
						res.objs = append(res.objs, obj)
					}
				}
			}
		}
		if funcDecl, ok := dec.(*ast.FuncDecl); ok {
			if funcDecl.Recv == nil {
				continue
			}
			if expr, ok := funcDecl.Recv.List[0].Type.(*ast.StarExpr); ok {
				if i, ok := expr.X.(*ast.Ident); ok {
					objName := i.Name
					if ok := isFuncDecl(funcDecl); ok {
						funcRefs[objName]++
					}
				}
			}
		}
	}
	for name, count := range funcRefs {
		if count == 4 {
			res.funcs = append(res.funcs, name)
		}
	}
	return res
}
func isSpecificFunc(funcDecl *ast.FuncDecl, in, out []string) bool {
	check := func(types *ast.FieldList, args []string) bool {
		list := types.List
		if len(list) != len(args) {
			return false
		}
		for i := 0; i < len(list); i++ {
			typ := list[i].Type
			arg := args[i]
			var buf bytes.Buffer
			fset := token.NewFileSet()
			if err := format.Node(&buf, fset, typ); err != nil {
				panic(err)
			}
			if string(buf.Bytes()) != arg {
				return false
			}
		}
		return true
	}
	if !check(funcDecl.Type.Params, in) {
		return false
	}
	if !check(funcDecl.Type.Results, out) {
		return false
	}
	return true
}
func isFuncDecl(funcDecl *ast.FuncDecl) bool {
	name := funcDecl.Name.Name
	if name == "SizeSSZ" {
		return isSpecificFunc(funcDecl, []string{}, []string{"int"})
	}
	if name == "MarshalSSZTo" {
		return isSpecificFunc(funcDecl, []string{"[]byte"}, []string{"[]byte", "error"})
	}
	if name == "UnmarshalSSZ" {
		return isSpecificFunc(funcDecl, []string{"[]byte"}, []string{"error"})
	}
	if name == "HashTreeRootWith" {
		return isSpecificFunc(funcDecl, []string{"*ssz.Hasher"}, []string{"error"})
	}
	return false
}
type astImport struct {
	alias string
	path  string
}
func (a *astImport) getFullName() string {
	if a.alias != "" {
		return fmt.Sprintf("%s \"%s\"", a.alias, a.path)
	}
	return fmt.Sprintf("\"%s\"", a.path)
}
func (a *astImport) match(name string) bool {
	if a.alias != "" {
		return a.alias == name
	}
	return filepath.Base(a.path) == name
}
func trimQuotes(a string) string {
	return strings.Trim(a, "\"")
}
func decodeASTImports(file *ast.File) []*astImport {
	imports := []*astImport{}
	for _, i := range file.Imports {
		var alias string
		if i.Name != nil {
			if i.Name.Name == "_" {
				continue
			}
			alias = i.Name.Name
		}
		path := trimQuotes(i.Path.Value)
		imports = append(imports, &astImport{
			alias: alias,
			path:  path,
		})
	}
	return imports
}
func (e *env) getRawItemByName(name string) (*astStruct, bool) {
	for _, item := range e.raw {
		if item.name == name {
			return item, true
		}
	}
	return nil, false
}
func (e *env) addRawItem(i *astStruct) {
	e.raw = append(e.raw, i)
}
func (e *env) generateIR() error {
	e.raw = []*astStruct{}
	e.order = map[string][]string{}
	e.imports = []*astImport{}
	checkObjByPackage := func(packName, name string) (*astStruct, bool) {
		for _, item := range e.raw {
			if item.name == name && item.packName == packName {
				return item, true
			}
		}
		return nil, false
	}
	addStructs := func(res *astResult, isRef bool) error {
		for _, i := range res.objs {
			if _, ok := checkObjByPackage(i.packName, i.name); ok {
				return fmt.Errorf("two structs share the same name %s", i.name)
			}
			i.isRef = isRef
			e.addRawItem(i)
		}
		return nil
	}
	checkImplFunc := func(res *astResult) error {
		for _, name := range res.funcs {
			v, ok := checkObjByPackage(res.packName, name)
			if !ok {
				return fmt.Errorf("cannot find %s struct", name)
			}
			v.implFunc = true
		}
		return nil
	}
	addImports := func(imports []*astImport) error {
		for _, i := range imports {
			found := false
			for _, j := range e.imports {
				if j.path == i.path {
					found = true
					if i.alias != j.alias {
						return fmt.Errorf("the same package is imported twice by different files of path %s and %s with different aliases: %s and %s", j.path, i.path, j.alias, i.alias)
					}
				}
			}
			if !found {
				e.imports = append(e.imports, i)
			}
		}
		return nil
	}
	for _, file := range e.files {
		if err := addImports(decodeASTImports(file)); err != nil {
			return err
		}
	}
	astResults := []*astResult{}
	for name, file := range e.files {
		res := decodeASTStruct(file)
		if err := addStructs(res, false); err != nil {
			return err
		}
		astResults = append(astResults, res)
		structOrdering := []string{}
		for _, i := range res.objs {
			structOrdering = append(structOrdering, i.name)
		}
		e.order[name] = structOrdering
	}
	for _, file := range e.include {
		res := decodeASTStruct(file)
		if err := addStructs(res, true); err != nil {
			return err
		}
		astResults = append(astResults, res)
	}
	for _, res := range astResults {
		if err := checkImplFunc(res); err != nil {
			return err
		}
	}
	for _, obj := range e.raw {
		name := obj.name
		var valid bool
		if e.targets == nil || len(e.targets) == 0 {
			valid = true
		} else {
			valid = contains(name, e.targets)
		}
		if valid {
			if obj.isRef {
				continue
			}
			if _, err := e.encodeItem(name, ""); err != nil {
				return err
			}
		}
	}
	return nil
}
func contains(i string, j []string) bool {
	for _, a := range j {
		if a == i {
			return true
		}
	}
	return false
}
func (e *env) encodeItem(name, tags string) (*Value, error) {
	v, ok := e.objs[name]
	if !ok {
		var err error
		raw, ok := e.getRawItemByName(name)
		if !ok {
			return nil, fmt.Errorf("could not find struct with name '%s'", name)
		}
		if raw.implFunc {
			size, _ := getTagsInt(tags, "ssz-size")
			v = &Value{t: TypeReference, s: size, n: size, noPtr: raw.obj == nil}
		} else if raw.obj != nil {
			v, err = e.parseASTStructType(name, raw.obj)
		} else {
			v, err = e.parseASTFieldType(name, tags, raw.typ)
		}
		if err != nil {
			return nil, fmt.Errorf("failed to encode %s: %v", name, err)
		}
		v.name = name
		v.obj = name
		e.objs[name] = v
	}
	return v.copy(), nil
}