mxh.mx raw
1 package mxh
2
3 import (
4 "git.smesh.lol/moxie/pkg/emit"
5 "git.smesh.lol/moxie/pkg/syntax"
6 "git.smesh.lol/moxie/pkg/token"
7 "git.smesh.lol/moxie/pkg/types"
8 )
9
10 // typeDescPkgPath controls whether named types include their package path
11 // in the descriptor. Set before calling TypeDescStr.
12 var typeDescPkgPath string
13
14 func TypeDescStr(t syntax.Type) (s string) {
15 if t == nil {
16 return "interface{}"
17 }
18 switch v := t.(type) {
19 case *types.Basic:
20 switch v.Kind() {
21 case types.Bool:
22 return "bool"
23 case types.Int8:
24 return "int8"
25 case types.Int16:
26 return "int16"
27 case types.Int32:
28 return "int32"
29 case types.Int64:
30 return "int64"
31 case types.Uint8:
32 return "uint8"
33 case types.Uint16:
34 return "uint16"
35 case types.Uint32:
36 return "uint32"
37 case types.Uint64:
38 return "uint64"
39 case types.Float32:
40 return "float32"
41 case types.Float64:
42 return "float64"
43 case types.TCString:
44 return "string"
45 case types.UnsafePointer:
46 return "ptr"
47 }
48 return "int32"
49 case *types.Slice:
50 return "[]" | TypeDescStr(v.Elem())
51 case *types.Array:
52 return "[" | token.Itoa64(v.Len()) | "]" | TypeDescStr(v.Elem())
53 case *types.Pointer:
54 return "*" | TypeDescStr(v.Elem())
55 case *types.TCStruct:
56 s := "struct{"
57 for i := int32(0); i < v.NumFields(); i++ {
58 if i > 0 {
59 s = s | ","
60 }
61 f := v.Field(i)
62 s = s | f.Name() | ":" | TypeDescStr(f.Type())
63 }
64 return s | "}"
65 case *types.TCInterface:
66 if v.IsEmpty() {
67 return "interface{}"
68 }
69 if v.NumMethods() == 1 && v.Method(0).Name() == "Error" {
70 return "error"
71 }
72 return "interface{}"
73 case *types.TCChan:
74 switch v.Dir() {
75 case types.TCSendOnly:
76 return "chan<-:" | TypeDescStr(v.Elem())
77 case types.TCRecvOnly:
78 return "<-chan:" | TypeDescStr(v.Elem())
79 }
80 return "chan:" | TypeDescStr(v.Elem())
81 case *types.TCMap:
82 return "map[" | TypeDescStr(v.Key()) | "]" | TypeDescStr(v.Elem())
83 case *types.Named:
84 if v.Obj() != nil {
85 if typeDescPkgPath != "" && v.Obj().Pkg() != nil && v.Obj().Pkg().Path() != typeDescPkgPath {
86 return v.Obj().Pkg().Path() | "." | v.Obj().Name()
87 }
88 return v.Obj().Name()
89 }
90 return TypeDescStr(v.RawUnderlying())
91 }
92 return "interface{}"
93 }
94
95 func SigDescStr(sig *types.Signature) (s string) {
96 var out string
97 if sig.Params() != nil {
98 for i := int32(0); i < sig.Params().Len(); i++ {
99 if i > 0 {
100 out = out | ","
101 }
102 out = out | TypeDescStr(sig.Params().At(i).Type())
103 }
104 }
105 if sig.Results() != nil && sig.Results().Len() > 0 {
106 out = out | "->"
107 for i := int32(0); i < sig.Results().Len(); i++ {
108 if i > 0 {
109 out = out | ","
110 }
111 out = out | TypeDescStr(sig.Results().At(i).Type())
112 }
113 }
114 return out
115 }
116
117 func GenerateMxh(pkg *types.TCPackage) (s string) {
118 path := pkg.Path()
119 name := pkg.Name()
120 typeDescPkgPath = path
121 var out string
122 out = "package " | path | " " | name | "\n"
123 for _, objName := range pkg.Scope().Names() {
124 if len(objName) == 0 {
125 continue
126 }
127 exported := objName[0] >= 'A' && objName[0] <= 'Z'
128 obj := pkg.Scope().Lookup(objName)
129 if obj == nil {
130 continue
131 }
132 if !exported {
133 if tn, ok := obj.(*types.TypeName); ok {
134 if named, ok2 := tn.Type().(*types.Named); ok2 && named.NumMethods() > 0 {
135 // include unexported types with methods for interface dispatch
136 } else {
137 continue
138 }
139 } else {
140 continue
141 }
142 }
143 switch v := obj.(type) {
144 case *types.TCFunc:
145 sig := v.Signature()
146 if sig != nil {
147 out = out | "func " | objName | " " | SigDescStr(sig) | "\n"
148 }
149 case *types.TCVar:
150 out = out | "var " | objName | " " | TypeDescStr(v.Type()) | "\n"
151 case *types.TCConst:
152 td := TypeDescStr(v.Type())
153 vs := ""
154 if v.Val() != nil {
155 vs = v.Val().String()
156 }
157 out = out | "const " | objName | " " | td | " " | vs | "\n"
158 case *types.TypeName:
159 typ := v.Type()
160 if named, ok := typ.(*types.Named); ok {
161 if ui, ok2 := named.RawUnderlying().(*types.TCInterface); ok2 && !ui.IsEmpty() {
162 var methods string
163 for mi := int32(0); mi < ui.NumMethods(); mi++ {
164 m := ui.Method(mi)
165 if mi > 0 {
166 methods = methods | ";"
167 }
168 methods = methods | m.Name() | "=" | SigDescStr(m.Sig())
169 }
170 out = out | "iface " | objName | " " | methods | "\n"
171 } else {
172 ud := ""
173 if named.RawUnderlying() != nil {
174 ud = TypeDescStr(named.RawUnderlying())
175 }
176 out = out | "type " | objName | " " | ud | "\n"
177 for _, m := range named.Methods() {
178 ptrRecv := "0"
179 if m.HasPtrRecv() {
180 ptrRecv = "1"
181 }
182 msig := m.Signature()
183 if msig != nil {
184 out = out | "method " | objName | " " | m.Name() | " " | SigDescStr(msig) | " " | ptrRecv | "\n"
185 }
186 }
187 }
188 } else if iface, ok := typ.(*types.TCInterface); ok {
189 var methods string
190 for mi := int32(0); mi < iface.NumMethods(); mi++ {
191 m := iface.Method(mi)
192 if mi > 0 {
193 methods = methods | ";"
194 }
195 methods = methods | m.Name() | "=" | SigDescStr(m.Sig())
196 }
197 out = out | "iface " | objName | " " | methods | "\n"
198 }
199 }
200 }
201 return out
202 }
203
204 func LoadMxh(data string, registry map[string]*types.TCPackage) (ok bool) {
205 lines := splitLines(data)
206 if len(lines) == 0 {
207 return false
208 }
209 first := lines[0]
210 if !hasPrefix(first, "package ") {
211 return false
212 }
213 rest := first[8:]
214 spaceIdx := int32(-1)
215 for i := int32(0); i < int32(len(rest)); i++ {
216 if rest[i] == ' ' {
217 spaceIdx = i
218 break
219 }
220 }
221 if spaceIdx < 0 {
222 return false
223 }
224 pkgPath := rest[:spaceIdx]
225 pkgName := rest[spaceIdx+1:]
226 pkg := types.NewTCPackage(pkgPath, pkgName)
227
228 oldScope := emit.ParseTypePkgScope
229 emit.ParseTypePkgScope = pkg.Scope()
230
231 // Save and set ImportRegistry so ParseTypeDesc can resolve cross-package refs
232 oldRegistry := emit.ImportRegistry
233 emit.ImportRegistry = registry
234
235 // Pass 1a: register all type and interface names with placeholder types
236 for li := 1; li < len(lines); li++ {
237 line := lines[li]
238 if hasPrefix(line, "type ") {
239 parts := splitBySpace(line[5:])
240 if len(parts) >= 1 {
241 tn := types.NewTypeName(pkg, parts[0], nil)
242 types.NewNamed(tn, nil)
243 pkg.Scope().Insert(tn)
244 }
245 } else if hasPrefix(line, "iface ") {
246 parts := splitBySpace(line[6:])
247 if len(parts) >= 1 {
248 placeholder := types.NewTCInterface(nil, nil)
249 placeholder.Complete()
250 tn := types.NewTypeName(pkg, parts[0], nil)
251 types.NewNamed(tn, placeholder)
252 pkg.Scope().Insert(tn)
253 }
254 }
255 }
256
257 // Pass 1b: resolve underlying types and interface methods (all names exist now)
258 for li := 1; li < len(lines); li++ {
259 line := lines[li]
260 if hasPrefix(line, "type ") {
261 parts := splitBySpace(line[5:])
262 if len(parts) >= 2 {
263 underlying := emit.ParseTypeDesc(parts[1])
264 obj := pkg.Scope().Lookup(parts[0])
265 if tn, ok := obj.(*types.TypeName); ok {
266 if named, ok2 := tn.Type().(*types.Named); ok2 {
267 named.SetUnderlying(underlying)
268 }
269 }
270 } else if len(parts) == 1 {
271 obj := pkg.Scope().Lookup(parts[0])
272 if tn, ok := obj.(*types.TypeName); ok {
273 if named, ok2 := tn.Type().(*types.Named); ok2 {
274 named.SetUnderlying(types.NewTCStruct(nil, nil))
275 }
276 }
277 }
278 } else if hasPrefix(line, "iface ") {
279 parts := splitBySpace(line[6:])
280 if len(parts) >= 1 {
281 ifaceName := parts[0]
282 methodsDesc := ""
283 if len(parts) >= 2 {
284 methodsDesc = parts[1]
285 }
286 var methods []*types.IfaceMethod
287 if methodsDesc != "" {
288 mparts := emit.SplitSemicolon(methodsDesc)
289 for _, p := range mparts {
290 eqIdx := int32(-1)
291 for i := int32(0); i < int32(len(p)); i++ {
292 if p[i] == '=' {
293 eqIdx = i
294 break
295 }
296 }
297 if eqIdx < 0 {
298 continue
299 }
300 mname := p[:eqIdx]
301 msig := emit.ParseSignatureDesc(p[eqIdx+1:])
302 methods = append(methods, types.NewTCIfaceMethod(mname, msig))
303 }
304 }
305 iface := types.NewTCInterface(methods, nil)
306 iface.Complete()
307 obj := pkg.Scope().Lookup(ifaceName)
308 if tn, ok := obj.(*types.TypeName); ok {
309 if named, ok2 := tn.Type().(*types.Named); ok2 {
310 named.SetUnderlying(iface)
311 } else {
312 tn.SetType(iface)
313 }
314 }
315 }
316 }
317 }
318
319 // Pass 2: funcs, methods, vars, consts can now resolve types from pass 1
320 for li := 1; li < len(lines); li++ {
321 line := lines[li]
322 if hasPrefix(line, "func ") {
323 parts := splitBySpace(line[5:])
324 if len(parts) >= 2 {
325 sig := emit.ParseSignatureDesc(parts[1])
326 pkg.Scope().Insert(types.NewTCFunc(pkg, parts[0], sig))
327 } else if len(parts) == 1 {
328 sig := emit.ParseSignatureDesc("")
329 pkg.Scope().Insert(types.NewTCFunc(pkg, parts[0], sig))
330 }
331 } else if hasPrefix(line, "var ") {
332 parts := splitBySpace(line[4:])
333 if len(parts) >= 2 {
334 typ := emit.ParseTypeDesc(parts[1])
335 pkg.Scope().Insert(types.NewTCVar(pkg, parts[0], typ))
336 }
337 } else if hasPrefix(line, "const ") {
338 parts := splitBySpace(line[6:])
339 if len(parts) >= 2 {
340 typ := emit.ParseTypeDesc(parts[1])
341 var val types.ConstVal
342 if len(parts) >= 3 {
343 rest := parts[2]
344 for k := int32(3); k < int32(len(parts)); k++ {
345 rest = rest | " " | parts[k]
346 }
347 isNum := true
348 for ci := int32(0); ci < int32(len(rest)); ci++ {
349 ch := rest[ci]
350 if !((ch >= '0' && ch <= '9') || ch == '-' || ch == '+' || ch == 'x' || ch == 'X' || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
351 isNum = false
352 break
353 }
354 }
355 if isNum {
356 n := types.ParseIntLocal(rest)
357 val = types.NewConstInt(n)
358 } else {
359 if b, ok := typ.(*types.Basic); ok && (b.Kind() == types.TCString || b.Kind() == types.UntypedString) {
360 val = types.NewConstStr(rest)
361 } else {
362 val = types.NewConstInt(0)
363 }
364 }
365 } else {
366 val = types.NewConstInt(0)
367 }
368 pkg.Scope().Insert(types.NewTCConst(pkg, parts[0], typ, val))
369 }
370 } else if hasPrefix(line, "method ") {
371 parts := splitBySpace(line[7:])
372 if len(parts) >= 3 {
373 typeName := parts[0]
374 methodName := parts[1]
375 sigDesc := parts[2]
376 ptrRecv := false
377 if len(parts) >= 4 && parts[3] == "1" {
378 ptrRecv = true
379 }
380 obj := pkg.Scope().Lookup(typeName)
381 if obj != nil {
382 if tn, ok := obj.(*types.TypeName); ok {
383 if named, ok := tn.Type().(*types.Named); ok {
384 sig := emit.ParseSignatureDesc(sigDesc)
385 var recvType syntax.Type = named
386 if ptrRecv {
387 recvType = types.NewPointer(named)
388 }
389 sig = types.NewSignature(types.NewTCVar(pkg, "", recvType), sig.Params(), sig.Results(), sig.Variadic())
390 fn := types.NewTCFunc(pkg, methodName, sig)
391 fn.SetPtrRecv(ptrRecv)
392 named.AddMethod(fn)
393 }
394 }
395 }
396 }
397 }
398 }
399
400 emit.ParseTypePkgScope = oldScope
401 emit.ImportRegistry = oldRegistry
402 registry[pkgPath] = pkg
403 return true
404 }
405
406 func CachedMxhPath(cacheBase, pkgPath, hash, version string) (s string) {
407 return joinPath(joinPath(cacheBase, safeName(pkgPath)), versionedCacheName(hash, version, ".mxh"))
408 }
409
410 // -- local helpers --
411
412 func splitLines(s string) (ss []string) {
413 var result []string
414 start := int32(0)
415 for i := int32(0); i < int32(len(s)); i++ {
416 if s[i] == '\n' {
417 if i > start {
418 result = append(result, s[start:i])
419 }
420 start = i + 1
421 }
422 }
423 if start < int32(len(s)) {
424 result = append(result, s[start:])
425 }
426 return result
427 }
428
429 func hasPrefix(s, prefix string) (ok bool) {
430 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
431 }
432
433 func splitBySpace(s string) (ss []string) {
434 var result []string
435 start := int32(0)
436 inWord := false
437 for i := int32(0); i < int32(len(s)); i++ {
438 if s[i] == ' ' || s[i] == '\t' {
439 if inWord {
440 result = append(result, s[start:i])
441 inWord = false
442 }
443 } else {
444 if !inWord {
445 start = i
446 inWord = true
447 }
448 }
449 }
450 if inWord {
451 result = append(result, s[start:])
452 }
453 return result
454 }
455
456 func joinPath(a, b string) (s string) {
457 if len(a) == 0 {
458 return b
459 }
460 if a[len(a)-1] == '/' {
461 return a | b
462 }
463 return a | "/" | b
464 }
465
466 func safeName(pkg string) (s string) {
467 b := []byte{:len(pkg)}
468 for i := int32(0); i < int32(len(pkg)); i++ {
469 if pkg[i] == '/' {
470 b[i] = '_'
471 } else {
472 b[i] = pkg[i]
473 }
474 }
475 return string(b)
476 }
477
478 func versionedCacheName(hash, version, ext string) (s string) {
479 if version != "" {
480 return hash | "_" | version | ext
481 }
482 return hash | ext
483 }
484
485