package mxh import ( "git.smesh.lol/moxie/pkg/emit" "git.smesh.lol/moxie/pkg/syntax" "git.smesh.lol/moxie/pkg/token" "git.smesh.lol/moxie/pkg/types" ) // typeDescPkgPath controls whether named types include their package path // in the descriptor. Set before calling TypeDescStr. var typeDescPkgPath string func TypeDescStr(t syntax.Type) (s string) { if t == nil { return "interface{}" } switch v := t.(type) { case *types.Basic: switch v.Kind() { case types.Bool: return "bool" case types.Int8: return "int8" case types.Int16: return "int16" case types.Int32: return "int32" case types.Int64: return "int64" case types.Uint8: return "uint8" case types.Uint16: return "uint16" case types.Uint32: return "uint32" case types.Uint64: return "uint64" case types.Float32: return "float32" case types.Float64: return "float64" case types.TCString: return "string" case types.UnsafePointer: return "ptr" } return "int32" case *types.Slice: return "[]" | TypeDescStr(v.Elem()) case *types.Array: return "[" | token.Itoa64(v.Len()) | "]" | TypeDescStr(v.Elem()) case *types.Pointer: return "*" | TypeDescStr(v.Elem()) case *types.TCStruct: s := "struct{" for i := int32(0); i < v.NumFields(); i++ { if i > 0 { s = s | "," } f := v.Field(i) s = s | f.Name() | ":" | TypeDescStr(f.Type()) } return s | "}" case *types.TCInterface: if v.IsEmpty() { return "interface{}" } if v.NumMethods() == 1 && v.Method(0).Name() == "Error" { return "error" } return "interface{}" case *types.TCChan: switch v.Dir() { case types.TCSendOnly: return "chan<-:" | TypeDescStr(v.Elem()) case types.TCRecvOnly: return "<-chan:" | TypeDescStr(v.Elem()) } return "chan:" | TypeDescStr(v.Elem()) case *types.TCMap: return "map[" | TypeDescStr(v.Key()) | "]" | TypeDescStr(v.Elem()) case *types.Named: if v.Obj() != nil { if typeDescPkgPath != "" && v.Obj().Pkg() != nil && v.Obj().Pkg().Path() != typeDescPkgPath { return v.Obj().Pkg().Path() | "." | v.Obj().Name() } return v.Obj().Name() } return TypeDescStr(v.RawUnderlying()) } return "interface{}" } func SigDescStr(sig *types.Signature) (s string) { var out string if sig.Params() != nil { for i := int32(0); i < sig.Params().Len(); i++ { if i > 0 { out = out | "," } out = out | TypeDescStr(sig.Params().At(i).Type()) } } if sig.Results() != nil && sig.Results().Len() > 0 { out = out | "->" for i := int32(0); i < sig.Results().Len(); i++ { if i > 0 { out = out | "," } out = out | TypeDescStr(sig.Results().At(i).Type()) } } return out } func GenerateMxh(pkg *types.TCPackage) (s string) { path := pkg.Path() name := pkg.Name() typeDescPkgPath = path var out string out = "package " | path | " " | name | "\n" for _, objName := range pkg.Scope().Names() { if len(objName) == 0 { continue } exported := objName[0] >= 'A' && objName[0] <= 'Z' obj := pkg.Scope().Lookup(objName) if obj == nil { continue } if !exported { if tn, ok := obj.(*types.TypeName); ok { if named, ok2 := tn.Type().(*types.Named); ok2 && named.NumMethods() > 0 { // include unexported types with methods for interface dispatch } else { continue } } else { continue } } switch v := obj.(type) { case *types.TCFunc: sig := v.Signature() if sig != nil { out = out | "func " | objName | " " | SigDescStr(sig) | "\n" } case *types.TCVar: out = out | "var " | objName | " " | TypeDescStr(v.Type()) | "\n" case *types.TCConst: td := TypeDescStr(v.Type()) vs := "" if v.Val() != nil { vs = v.Val().String() } out = out | "const " | objName | " " | td | " " | vs | "\n" case *types.TypeName: typ := v.Type() if named, ok := typ.(*types.Named); ok { if ui, ok2 := named.RawUnderlying().(*types.TCInterface); ok2 && !ui.IsEmpty() { var methods string for mi := int32(0); mi < ui.NumMethods(); mi++ { m := ui.Method(mi) if mi > 0 { methods = methods | ";" } methods = methods | m.Name() | "=" | SigDescStr(m.Sig()) } out = out | "iface " | objName | " " | methods | "\n" } else { ud := "" if named.RawUnderlying() != nil { ud = TypeDescStr(named.RawUnderlying()) } out = out | "type " | objName | " " | ud | "\n" for _, m := range named.Methods() { ptrRecv := "0" if m.HasPtrRecv() { ptrRecv = "1" } msig := m.Signature() if msig != nil { out = out | "method " | objName | " " | m.Name() | " " | SigDescStr(msig) | " " | ptrRecv | "\n" } } } } else if iface, ok := typ.(*types.TCInterface); ok { var methods string for mi := int32(0); mi < iface.NumMethods(); mi++ { m := iface.Method(mi) if mi > 0 { methods = methods | ";" } methods = methods | m.Name() | "=" | SigDescStr(m.Sig()) } out = out | "iface " | objName | " " | methods | "\n" } } } return out } func LoadMxh(data string, registry map[string]*types.TCPackage) (ok bool) { lines := splitLines(data) if len(lines) == 0 { return false } first := lines[0] if !hasPrefix(first, "package ") { return false } rest := first[8:] spaceIdx := int32(-1) for i := int32(0); i < int32(len(rest)); i++ { if rest[i] == ' ' { spaceIdx = i break } } if spaceIdx < 0 { return false } pkgPath := rest[:spaceIdx] pkgName := rest[spaceIdx+1:] pkg := types.NewTCPackage(pkgPath, pkgName) oldScope := emit.ParseTypePkgScope emit.ParseTypePkgScope = pkg.Scope() // Save and set ImportRegistry so ParseTypeDesc can resolve cross-package refs oldRegistry := emit.ImportRegistry emit.ImportRegistry = registry // Pass 1a: register all type and interface names with placeholder types for li := 1; li < len(lines); li++ { line := lines[li] if hasPrefix(line, "type ") { parts := splitBySpace(line[5:]) if len(parts) >= 1 { tn := types.NewTypeName(pkg, parts[0], nil) types.NewNamed(tn, nil) pkg.Scope().Insert(tn) } } else if hasPrefix(line, "iface ") { parts := splitBySpace(line[6:]) if len(parts) >= 1 { placeholder := types.NewTCInterface(nil, nil) placeholder.Complete() tn := types.NewTypeName(pkg, parts[0], nil) types.NewNamed(tn, placeholder) pkg.Scope().Insert(tn) } } } // Pass 1b: resolve underlying types and interface methods (all names exist now) for li := 1; li < len(lines); li++ { line := lines[li] if hasPrefix(line, "type ") { parts := splitBySpace(line[5:]) if len(parts) >= 2 { underlying := emit.ParseTypeDesc(parts[1]) obj := pkg.Scope().Lookup(parts[0]) if tn, ok := obj.(*types.TypeName); ok { if named, ok2 := tn.Type().(*types.Named); ok2 { named.SetUnderlying(underlying) } } } else if len(parts) == 1 { obj := pkg.Scope().Lookup(parts[0]) if tn, ok := obj.(*types.TypeName); ok { if named, ok2 := tn.Type().(*types.Named); ok2 { named.SetUnderlying(types.NewTCStruct(nil, nil)) } } } } else if hasPrefix(line, "iface ") { parts := splitBySpace(line[6:]) if len(parts) >= 1 { ifaceName := parts[0] methodsDesc := "" if len(parts) >= 2 { methodsDesc = parts[1] } var methods []*types.IfaceMethod if methodsDesc != "" { mparts := emit.SplitSemicolon(methodsDesc) for _, p := range mparts { eqIdx := int32(-1) for i := int32(0); i < int32(len(p)); i++ { if p[i] == '=' { eqIdx = i break } } if eqIdx < 0 { continue } mname := p[:eqIdx] msig := emit.ParseSignatureDesc(p[eqIdx+1:]) methods = append(methods, types.NewTCIfaceMethod(mname, msig)) } } iface := types.NewTCInterface(methods, nil) iface.Complete() obj := pkg.Scope().Lookup(ifaceName) if tn, ok := obj.(*types.TypeName); ok { if named, ok2 := tn.Type().(*types.Named); ok2 { named.SetUnderlying(iface) } else { tn.SetType(iface) } } } } } // Pass 2: funcs, methods, vars, consts can now resolve types from pass 1 for li := 1; li < len(lines); li++ { line := lines[li] if hasPrefix(line, "func ") { parts := splitBySpace(line[5:]) if len(parts) >= 2 { sig := emit.ParseSignatureDesc(parts[1]) pkg.Scope().Insert(types.NewTCFunc(pkg, parts[0], sig)) } else if len(parts) == 1 { sig := emit.ParseSignatureDesc("") pkg.Scope().Insert(types.NewTCFunc(pkg, parts[0], sig)) } } else if hasPrefix(line, "var ") { parts := splitBySpace(line[4:]) if len(parts) >= 2 { typ := emit.ParseTypeDesc(parts[1]) pkg.Scope().Insert(types.NewTCVar(pkg, parts[0], typ)) } } else if hasPrefix(line, "const ") { parts := splitBySpace(line[6:]) if len(parts) >= 2 { typ := emit.ParseTypeDesc(parts[1]) var val types.ConstVal if len(parts) >= 3 { rest := parts[2] for k := int32(3); k < int32(len(parts)); k++ { rest = rest | " " | parts[k] } isNum := true for ci := int32(0); ci < int32(len(rest)); ci++ { ch := rest[ci] if !((ch >= '0' && ch <= '9') || ch == '-' || ch == '+' || ch == 'x' || ch == 'X' || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) { isNum = false break } } if isNum { n := types.ParseIntLocal(rest) val = types.NewConstInt(n) } else { if b, ok := typ.(*types.Basic); ok && (b.Kind() == types.TCString || b.Kind() == types.UntypedString) { val = types.NewConstStr(rest) } else { val = types.NewConstInt(0) } } } else { val = types.NewConstInt(0) } pkg.Scope().Insert(types.NewTCConst(pkg, parts[0], typ, val)) } } else if hasPrefix(line, "method ") { parts := splitBySpace(line[7:]) if len(parts) >= 3 { typeName := parts[0] methodName := parts[1] sigDesc := parts[2] ptrRecv := false if len(parts) >= 4 && parts[3] == "1" { ptrRecv = true } obj := pkg.Scope().Lookup(typeName) if obj != nil { if tn, ok := obj.(*types.TypeName); ok { if named, ok := tn.Type().(*types.Named); ok { sig := emit.ParseSignatureDesc(sigDesc) var recvType syntax.Type = named if ptrRecv { recvType = types.NewPointer(named) } sig = types.NewSignature(types.NewTCVar(pkg, "", recvType), sig.Params(), sig.Results(), sig.Variadic()) fn := types.NewTCFunc(pkg, methodName, sig) fn.SetPtrRecv(ptrRecv) named.AddMethod(fn) } } } } } } emit.ParseTypePkgScope = oldScope emit.ImportRegistry = oldRegistry registry[pkgPath] = pkg return true } func CachedMxhPath(cacheBase, pkgPath, hash, version string) (s string) { return joinPath(joinPath(cacheBase, safeName(pkgPath)), versionedCacheName(hash, version, ".mxh")) } // -- local helpers -- func splitLines(s string) (ss []string) { var result []string start := int32(0) for i := int32(0); i < int32(len(s)); i++ { if s[i] == '\n' { if i > start { result = append(result, s[start:i]) } start = i + 1 } } if start < int32(len(s)) { result = append(result, s[start:]) } return result } func hasPrefix(s, prefix string) (ok bool) { return len(s) >= len(prefix) && s[:len(prefix)] == prefix } func splitBySpace(s string) (ss []string) { var result []string start := int32(0) inWord := false for i := int32(0); i < int32(len(s)); i++ { if s[i] == ' ' || s[i] == '\t' { if inWord { result = append(result, s[start:i]) inWord = false } } else { if !inWord { start = i inWord = true } } } if inWord { result = append(result, s[start:]) } return result } func joinPath(a, b string) (s string) { if len(a) == 0 { return b } if a[len(a)-1] == '/' { return a | b } return a | "/" | b } func safeName(pkg string) (s string) { b := []byte{:len(pkg)} for i := int32(0); i < int32(len(pkg)); i++ { if pkg[i] == '/' { b[i] = '_' } else { b[i] = pkg[i] } } return string(b) } func versionedCacheName(hash, version, ext string) (s string) { if version != "" { return hash | "_" | version | ext } return hash | ext }