package main import ( "encoding/json" "fmt" "os" "path/filepath" "strconv" "strings" ) var builtin = map[string]*Type{} func init() { for _, b := range []struct { xml, goType string size int }{ {"CARD8", "uint8", 1}, {"CARD16", "uint16", 2}, {"CARD32", "uint32", 4}, {"CARD64", "uint64", 8}, {"INT8", "int8", 1}, {"INT16", "int16", 2}, {"INT32", "int32", 4}, {"INT64", "int64", 8}, {"BOOL", "bool", 1}, {"BOOL32", "bool", 4}, {"char", "byte", 1}, {"void", "byte", 1}, {"BYTE", "byte", 1}, } { builtin[b.xml] = &Type{Name: b.xml, GoType: b.goType, Base: b.goType, Size: b.size, Kind: "scalar", Resolved: true} } } func loadModules() ([]*Module, error) { var manifest struct { Files []struct { Path string `json:"path"` } `json:"files"` } b, err := os.ReadFile("spec/manifest.json") if err != nil { return nil, err } if err = json.Unmarshal(b, &manifest); err != nil { return nil, err } var nodes []*Node for _, f := range manifest.Files { if filepath.Ext(f.Path) != ".xml" { continue } n, err := parseFile(f.Path) if err != nil { return nil, err } nodes = append(nodes, n) } return resolveModules(nodes) } func (m *Module) claim(name string, n *Node) error { if old := m.Names[name]; old != nil { return n.fail("Go name collision %s (previously %s:%d)", name, old.File, old.Line) } m.Names[name] = n return nil } func resolveModules(nodes []*Node) ([]*Module, error) { var modules []*Module byHeader := map[string]*Module{} for _, n := range nodes { m := &Module{Header: n.Attr["header"], Pkg: strings.ReplaceAll(n.Attr["header"], "_", ""), XName: n.Attr["extension-xname"], ExtName: n.Attr["extension-name"], Root: n, Symbols: map[string]*Type{}, Names: map[string]*Node{}} if byHeader[m.Header] != nil { return nil, n.fail("duplicate module %s", m.Header) } byHeader[m.Header] = m modules = append(modules, m) for _, c := range n.Children { switch c.Kind { case "xidtype", "xidunion", "typedef", "struct", "union": raw := c.Attr["name"] if c.Kind == "typedef" { raw = c.Attr["newname"] } name := goName(raw) if err := m.claim(name, c); err != nil { return nil, err } typ := &Type{Name: raw, GoType: name, Kind: c.Kind, Node: c, Module: m} m.Types = append(m.Types, typ) m.Symbols[raw] = typ } } } for _, m := range modules { for _, n := range m.Root.Children { if n.Kind == "import" { dep := byHeader[n.Text] if dep == nil { return nil, n.fail("unknown import %s", n.Text) } m.Imports = append(m.Imports, dep) } } } for _, m := range modules { for _, typ := range m.Types { if err := resolveType(typ); err != nil { return nil, err } } } for _, m := range modules { if err := resolveEnums(m); err != nil { return nil, err } } // Re-resolve enum annotations after widths are known, without changing wire sizes. for _, m := range modules { for _, typ := range m.Types { for i := range typ.Fields { f := &typ.Fields[i] if f.Kind != "field" { continue } if enum := f.Node.Attr["enum"]; enum != "" { if e, owner := findEnum(m, enum); e != nil { f.GoType = qualified(m, owner, e.Name) } } if mask := f.Node.Attr["mask"]; mask != "" { if e, owner := findEnum(m, mask); e != nil { f.GoType = qualified(m, owner, e.Name) } } } } } return modules, nil } func lookup(m *Module, name string) (*Type, error) { if t := builtin[name]; t != nil { return t, nil } if strings.Contains(name, ":") { parts := strings.SplitN(name, ":", 2) for _, dep := range m.Imports { if dep.Header == parts[0] { return lookup(dep, parts[1]) } } return nil, m.Root.fail("unknown qualified type %s", name) } if t := m.Symbols[name]; t != nil { return t, nil } var found *Type for _, dep := range m.Imports { if t := dep.Symbols[name]; t != nil { if found != nil && found != t { return nil, m.Root.fail("ambiguous type %s", name) } found = t } } if found != nil { return found, nil } return nil, m.Root.fail("unknown type %s", name) } func qualified(m, owner *Module, name string) string { if owner == nil || owner == m { return name } return owner.Pkg + "." + name } func resolveType(t *Type) error { if t.Resolved { return nil } if t.Resolving { return t.Node.fail("recursive type %s", t.Name) } t.Resolving = true switch t.Kind { case "xidtype", "xidunion": t.Size = 4 t.Base = "uint32" case "typedef": base, err := lookup(t.Module, t.Node.Attr["oldname"]) if err != nil { return err } if err := resolveType(base); err != nil { return err } t.Size = base.Size t.Base = base.Base t.Variable = base.Variable default: off := 0 for _, n := range t.Node.Children { if n.Kind == "doc" { continue } f := Field{Name: goName(n.Attr["name"]), Param: paramName(n.Attr["name"]), Kind: n.Kind, Node: n, Off: off} if t.Kind == "union" { f.Off = 0 } switch n.Kind { case "pad": if n.Attr["align"] != "" { f.Align, _ = strconv.Atoi(n.Attr["align"]) f.Size = (-off) & (f.Align - 1) } else { f.Size, _ = strconv.Atoi(n.Attr["bytes"]) } case "field", "list": elem, err := lookup(t.Module, n.Attr["type"]) if err != nil { return n.fail("%v", err) } if err := resolveType(elem); err != nil { return err } f.Type = elem f.GoType = qualified(t.Module, elem.Module, elem.GoType) f.Size = elem.Size if n.Kind == "list" { if len(n.Children) > 0 { f.Expr = n.Children[0] } if f.Expr != nil && f.Expr.Kind == "value" && !elem.Variable { count, err := strconv.Atoi(f.Expr.Text) if err != nil || count < 0 { return n.fail("invalid constant list size") } f.Count = count f.Size *= count f.GoType = fmt.Sprintf("[%d]%s", count, f.GoType) } else { t.Variable = true f.Size = 0 f.GoType = "[]" + f.GoType if n.Attr["type"] == "char" { f.GoType = "string" } } } if elem.Variable { t.Variable = true } default: return n.fail("unsupported type member %s", n.Kind) } t.Fields = append(t.Fields, f) if t.Kind == "union" { if f.Size > off { off = f.Size } } else if !t.Variable { off += f.Size } } t.Size = off } t.Resolving = false t.Resolved = true return nil } func findEnum(m *Module, name string) (*Enum, *Module) { if strings.Contains(name, ":") { p := strings.SplitN(name, ":", 2) for _, dep := range m.Imports { if dep.Header == p[0] { return findEnum(dep, p[1]) } } return nil, nil } for i := range m.Enums { if m.Enums[i].Node.Attr["name"] == name { return &m.Enums[i], m } } for _, dep := range m.Imports { for i := range dep.Enums { if dep.Enums[i].Node.Attr["name"] == name { return &dep.Enums[i], dep } } } return nil, nil } func resolveEnums(m *Module) error { for _, n := range m.Root.Children { if n.Kind != "enum" { continue } e := Enum{Name: goName(n.Attr["name"]), Base: "uint32", Node: n} if old := m.Names[e.Name]; old != nil { if !contains("xidtype xidunion typedef", old.Kind) { return n.fail("Go name collision %s", e.Name) } e.Existing = true } else if err := m.claim(e.Name, n); err != nil { return err } var max int64 for _, item := range n.Children { if item.Kind == "doc" { continue } ex := item.Children[0] v, err := strconv.ParseInt(ex.Text, 0, 64) if err != nil { return ex.fail("invalid enum value") } if ex.Kind == "bit" { if v < 0 || v > 31 { return ex.fail("enum bit out of range") } v = 1 << uint(v) } if v < 0 || v > 0xffffffff { return ex.fail("enum value out of range") } if v > max { max = v } name := e.Name + goName(item.Attr["name"]) if err := m.claim(name, item); err != nil { return err } e.Items = append(e.Items, EnumItem{name, v}) } // Narrow only when every wire use has the same narrower width. width := 0 mixed := false var scan func(*Node) scan = func(x *Node) { if x.Attr["enum"] == n.Attr["name"] || x.Attr["mask"] == n.Attr["name"] || x.Attr["altenum"] == n.Attr["name"] { typ, err := lookup(m, x.Attr["type"]) if err == nil { if width != 0 && width != typ.Size { mixed = true } width = typ.Size } } for _, c := range x.Children { scan(c) } } scan(m.Root) if !mixed && width == 1 && max <= 255 { e.Base = "uint8" } if !mixed && width == 2 && max <= 65535 { e.Base = "uint16" } m.Enums = append(m.Enums, e) } return nil }