mxh.go raw

   1  package loader
   2  
   3  import (
   4  	"fmt"
   5  	"go/token"
   6  	"go/types"
   7  	"os"
   8  	"path/filepath"
   9  	"strconv"
  10  	"strings"
  11  
  12  	"moxie/goenv"
  13  	"moxie/parse"
  14  )
  15  
  16  // MXHProtocolsDir returns the path to the target-independent protocols
  17  // cache where .mxh files are stored.
  18  func MXHProtocolsDir() string {
  19  	return filepath.Join(goenv.Get("GOCACHE"), "mxinstall", "protocols")
  20  }
  21  
  22  // synthMXHPackage reads a .mxh file from path and returns a synthetic
  23  // *types.Package containing the declared struct types. Methods are not
  24  // declared; the compiler uses IsExternalMXH to identify these types.
  25  func synthMXHPackage(importPath, mxhPath string) (*types.Package, error) {
  26  	f, err := os.Open(mxhPath)
  27  	if err != nil {
  28  		return nil, err
  29  	}
  30  	defer f.Close()
  31  
  32  	mxh, err := parse.ParseMXH(f)
  33  	if err != nil {
  34  		return nil, fmt.Errorf("mxh %s: %w", mxhPath, err)
  35  	}
  36  
  37  	// Derive package name from the last segment of the import path.
  38  	pkgName := importPath
  39  	if i := strings.LastIndexByte(pkgName, '/'); i >= 0 {
  40  		pkgName = pkgName[i+1:]
  41  	}
  42  
  43  	pkg := types.NewPackage(importPath, pkgName)
  44  
  45  	for _, td := range mxh.Types {
  46  		var vars []*types.Var
  47  		for _, fd := range td.Fields {
  48  			ft, err := parseTypeString(fd.Type)
  49  			if err != nil {
  50  				return nil, fmt.Errorf("mxh %s type %s field %s: %w", mxhPath, td.Name, fd.Name, err)
  51  			}
  52  			vars = append(vars, types.NewVar(token.NoPos, pkg, fd.Name, ft))
  53  		}
  54  
  55  		underlying := types.NewStruct(vars, nil)
  56  		typeName := types.NewTypeName(token.NoPos, pkg, td.Name, nil)
  57  		types.NewNamed(typeName, underlying, nil)
  58  
  59  		// Export the type name into the package scope.
  60  		pkg.Scope().Insert(typeName)
  61  	}
  62  
  63  	pkg.MarkComplete()
  64  	return pkg, nil
  65  }
  66  
  67  // parseTypeString maps a type string from a .mxh file to a types.Type.
  68  // Supports all basic types and fixed-size arrays like [20]byte.
  69  func parseTypeString(s string) (types.Type, error) {
  70  	switch s {
  71  	case "bool":
  72  		return types.Typ[types.Bool], nil
  73  	case "int8":
  74  		return types.Typ[types.Int8], nil
  75  	case "int16":
  76  		return types.Typ[types.Int16], nil
  77  	case "int32", "int":
  78  		return types.Typ[types.Int32], nil
  79  	case "int64":
  80  		return types.Typ[types.Int64], nil
  81  	case "uint8", "byte":
  82  		return types.Typ[types.Uint8], nil
  83  	case "uint16":
  84  		return types.Typ[types.Uint16], nil
  85  	case "uint32", "uint":
  86  		return types.Typ[types.Uint32], nil
  87  	case "uint64":
  88  		return types.Typ[types.Uint64], nil
  89  	case "float32":
  90  		return types.Typ[types.Float32], nil
  91  	case "float64":
  92  		return types.Typ[types.Float64], nil
  93  	case "string":
  94  		return types.Typ[types.String], nil
  95  	}
  96  
  97  	// Array types: [N]elem
  98  	if strings.HasPrefix(s, "[") {
  99  		bracket := strings.Index(s, "]")
 100  		if bracket < 0 {
 101  			return nil, fmt.Errorf("invalid array type %q", s)
 102  		}
 103  		nStr := s[1:bracket]
 104  		n, err := strconv.ParseInt(nStr, 10, 64)
 105  		if err != nil {
 106  			return nil, fmt.Errorf("invalid array length in %q: %w", s, err)
 107  		}
 108  		elem, err := parseTypeString(s[bracket+1:])
 109  		if err != nil {
 110  			return nil, fmt.Errorf("invalid array element in %q: %w", s, err)
 111  		}
 112  		return types.NewArray(elem, n), nil
 113  	}
 114  
 115  	return nil, fmt.Errorf("unsupported type %q in .mxh (only fixed-width scalar and array types are allowed)", s)
 116  }
 117  
 118  // mxhHashForImport returns the hash from the cached .mxh for importPath,
 119  // or an empty string if no .mxh is cached.
 120  func MXHHashForImport(importPath string) string {
 121  	mxhPath := filepath.Join(MXHProtocolsDir(), importPath+".mxh")
 122  	f, err := os.Open(mxhPath)
 123  	if err != nil {
 124  		return ""
 125  	}
 126  	defer f.Close()
 127  	mxh, err := parse.ParseMXH(f)
 128  	if err != nil {
 129  		return ""
 130  	}
 131  	return mxh.Hash
 132  }
 133