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  // mxhLibraries maps import paths to their shared library paths for FFI packages.
  23  var mxhLibraries = map[string]string{}
  24  
  25  // mxhSymbols maps "importpath.FuncName" to the C symbol name.
  26  var mxhSymbols = map[string]string{}
  27  
  28  // MXHLibrary returns the shared library path for an mxh FFI package,
  29  // or empty string if the package is not an FFI package.
  30  func MXHLibrary(importPath string) string {
  31  	return mxhLibraries[importPath]
  32  }
  33  
  34  // MXHSymbol returns the C symbol name for an mxh FFI function.
  35  func MXHSymbol(importPath, funcName string) string {
  36  	return mxhSymbols[importPath+"."+funcName]
  37  }
  38  
  39  // synthMXHPackage reads a .mxh file from path and returns a synthetic
  40  // *types.Package containing declared struct types and FFI function signatures.
  41  func synthMXHPackage(importPath, mxhPath string) (*types.Package, error) {
  42  	f, err := os.Open(mxhPath)
  43  	if err != nil {
  44  		return nil, err
  45  	}
  46  	defer f.Close()
  47  
  48  	mxh, err := parse.ParseMXH(f)
  49  	if err != nil {
  50  		return nil, fmt.Errorf("mxh %s: %w", mxhPath, err)
  51  	}
  52  
  53  	pkgName := importPath
  54  	if i := strings.LastIndexByte(pkgName, '/'); i >= 0 {
  55  		pkgName = pkgName[i+1:]
  56  	}
  57  
  58  	pkg := types.NewPackage(importPath, pkgName)
  59  
  60  	if mxh.Library != "" {
  61  		mxhLibraries[importPath] = mxh.Library
  62  	}
  63  
  64  	for _, td := range mxh.Types {
  65  		var vars []*types.Var
  66  		for _, fd := range td.Fields {
  67  			ft, err := parseTypeString(fd.Type)
  68  			if err != nil {
  69  				return nil, fmt.Errorf("mxh %s type %s field %s: %w", mxhPath, td.Name, fd.Name, err)
  70  			}
  71  			vars = append(vars, types.NewVar(token.NoPos, pkg, fd.Name, ft))
  72  		}
  73  
  74  		underlying := types.NewStruct(vars, nil)
  75  		typeName := types.NewTypeName(token.NoPos, pkg, td.Name, nil)
  76  		types.NewNamed(typeName, underlying, nil)
  77  		pkg.Scope().Insert(typeName)
  78  	}
  79  
  80  	for _, fd := range mxh.Funcs {
  81  		var params []*types.Var
  82  		for _, p := range fd.Params {
  83  			pt, err := parseTypeString(p.Type)
  84  			if err != nil {
  85  				return nil, fmt.Errorf("mxh %s func %s param %s: %w", mxhPath, fd.Name, p.Name, err)
  86  			}
  87  			params = append(params, types.NewVar(token.NoPos, pkg, p.Name, pt))
  88  		}
  89  		var results []*types.Var
  90  		for _, r := range fd.Results {
  91  			rt, err := parseTypeString(r.Type)
  92  			if err != nil {
  93  				return nil, fmt.Errorf("mxh %s func %s result: %w", mxhPath, fd.Name, err)
  94  			}
  95  			results = append(results, types.NewVar(token.NoPos, pkg, r.Name, rt))
  96  		}
  97  		sig := types.NewSignatureType(nil, nil, nil,
  98  			types.NewTuple(params...),
  99  			types.NewTuple(results...),
 100  			false)
 101  		fn := types.NewFunc(token.NoPos, pkg, fd.Name, sig)
 102  		pkg.Scope().Insert(fn)
 103  		mxhSymbols[importPath+"."+fd.Name] = fd.Symbol
 104  	}
 105  
 106  	pkg.MarkComplete()
 107  	return pkg, nil
 108  }
 109  
 110  // parseTypeString maps a type string from a .mxh file to a types.Type.
 111  // Supports all basic types and fixed-size arrays like [20]byte.
 112  func parseTypeString(s string) (types.Type, error) {
 113  	switch s {
 114  	case "bool":
 115  		return types.Typ[types.Bool], nil
 116  	case "int8":
 117  		return types.Typ[types.Int8], nil
 118  	case "int16":
 119  		return types.Typ[types.Int16], nil
 120  	case "int32", "int":
 121  		return types.Typ[types.Int32], nil
 122  	case "int64":
 123  		return types.Typ[types.Int64], nil
 124  	case "uint8", "byte":
 125  		return types.Typ[types.Uint8], nil
 126  	case "uint16":
 127  		return types.Typ[types.Uint16], nil
 128  	case "uint32", "uint":
 129  		return types.Typ[types.Uint32], nil
 130  	case "uint64":
 131  		return types.Typ[types.Uint64], nil
 132  	case "float32":
 133  		return types.Typ[types.Float32], nil
 134  	case "float64":
 135  		return types.Typ[types.Float64], nil
 136  	case "string":
 137  		return types.Typ[types.String], nil
 138  	}
 139  
 140  	// Array types: [N]elem
 141  	if strings.HasPrefix(s, "[") {
 142  		bracket := strings.Index(s, "]")
 143  		if bracket < 0 {
 144  			return nil, fmt.Errorf("invalid array type %q", s)
 145  		}
 146  		nStr := s[1:bracket]
 147  		n, err := strconv.ParseInt(nStr, 10, 64)
 148  		if err != nil {
 149  			return nil, fmt.Errorf("invalid array length in %q: %w", s, err)
 150  		}
 151  		elem, err := parseTypeString(s[bracket+1:])
 152  		if err != nil {
 153  			return nil, fmt.Errorf("invalid array element in %q: %w", s, err)
 154  		}
 155  		return types.NewArray(elem, n), nil
 156  	}
 157  
 158  	return nil, fmt.Errorf("unsupported type %q in .mxh (only fixed-width scalar and array types are allowed)", s)
 159  }
 160  
 161  // SynthMXHPackageForCheck loads the cached .mxh for importPath and returns
 162  // a synthetic *types.Package. Used by the API stability checker.
 163  func SynthMXHPackageForCheck(importPath string) (*types.Package, error) {
 164  	mxhPath := filepath.Join(MXHProtocolsDir(), importPath+".mxh")
 165  	return synthMXHPackage(importPath, mxhPath)
 166  }
 167  
 168  // MXHHashForImport returns the hash from the cached .mxh for importPath,
 169  // or an empty string if no .mxh is cached.
 170  func MXHHashForImport(importPath string) string {
 171  	mxhPath := filepath.Join(MXHProtocolsDir(), importPath+".mxh")
 172  	f, err := os.Open(mxhPath)
 173  	if err != nil {
 174  		return ""
 175  	}
 176  	defer f.Close()
 177  	mxh, err := parse.ParseMXH(f)
 178  	if err != nil {
 179  		return ""
 180  	}
 181  	return mxh.Hash
 182  }
 183  
 184  // MXHVersionForImport returns the module version from the cached .mxh for
 185  // importPath, or an empty string if unavailable.
 186  func MXHVersionForImport(importPath string) string {
 187  	mxhPath := filepath.Join(MXHProtocolsDir(), importPath+".mxh")
 188  	f, err := os.Open(mxhPath)
 189  	if err != nil {
 190  		return ""
 191  	}
 192  	defer f.Close()
 193  	mxh, err := parse.ParseMXH(f)
 194  	if err != nil {
 195  		return ""
 196  	}
 197  	return mxh.Version
 198  }
 199