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