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