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