package main import ( "fmt" "path/filepath" "strconv" "strings" ) type Request struct { Name string Opcode uint8 DataByte *Field Fixed int Params, Fields []Field Node *Node Derived map[string]string Lists []Field Values *ValueList } // resolveRequests lowers scalar, list and mask-bitcase value-list requests. func resolveRequests(m *Module) ([]Request, error) { var out []Request for _, n := range m.Root.Children { if n.Kind != "request" { continue } name := goName(n.Attr["name"]) if err := m.claim(name, n); err != nil { return nil, err } opcode, _ := strconv.Atoi(n.Attr["opcode"]) r := Request{Name: name, Opcode: uint8(opcode), Node: n, Derived: map[string]string{}} var err error r.Values, err = resolveValues(m, n, name) if err != nil { return nil, err } refs := map[string]int{} var countRefs func(*Node) countRefs = func(x *Node) { if x.Kind == "reply" || x.Kind == "doc" { return } if x.Kind == "fieldref" { refs[x.Text]++ } for _, c := range x.Children { countRefs(c) } } countRefs(n) for _, x := range n.Children { if x.Kind == "list" && len(x.Children) == 1 && x.Children[0].Kind == "fieldref" && refs[x.Children[0].Text] == 1 { r.Derived[x.Children[0].Text] = paramName(x.Attr["name"]) } } dynamic := false off := 4 first := true for _, n := range n.Children { if n.Kind == "reply" || n.Kind == "doc" { continue } if n.Kind == "switch" { r.Params = append(r.Params, Field{Param: "v", GoType: "*" + name + "Values"}) continue } f := Field{Name: goName(n.Attr["name"]), Param: paramName(n.Attr["name"]), Kind: n.Kind, Node: n, Off: off - 4} if n.Kind == "pad" { if n.Attr["align"] != "" { a, _ := strconv.Atoi(n.Attr["align"]) f.Size = (-off) & (a - 1) } else { f.Size, _ = strconv.Atoi(n.Attr["bytes"]) } } else { typ, err := lookup(m, n.Attr["type"]) if err != nil { return nil, n.fail("%v", err) } if typ.Variable && n.Kind != "list" { return nil, n.fail("variable field requires list support") } f.Type = typ f.Size = typ.Size f.GoType = qualified(m, typ.Module, typ.GoType) if enum := n.Attr["enum"]; enum != "" { e, owner := findEnum(m, enum) if e == nil { return nil, n.fail("unknown enum %s", enum) } f.GoType = qualified(m, owner, e.Name) } if mask := n.Attr["mask"]; mask != "" { e, owner := findEnum(m, mask) if e == nil { return nil, n.fail("unknown mask %s", mask) } f.GoType = qualified(m, owner, e.Name) } if n.Kind == "list" { f.Size = 0 f.GoType = "[]" + f.GoType if n.Attr["type"] == "char" { f.GoType = "string" } if n.Attr["type"] == "void" || n.Attr["type"] == "BYTE" || n.Attr["type"] == "CARD8" { f.GoType = "[]byte" } if len(n.Children) > 0 { f.Expr = n.Children[0] } if m.Header == "xproto" && r.Name == "SendEvent" { f.GoType = "*[32]byte" } r.Lists = append(r.Lists, f) r.Params = append(r.Params, f) } else if n.Kind == "field" && r.Derived[n.Attr["name"]] == "" { if r.Values != nil && n == r.Values.Mask.Node { f.Param = "v.Mask" } else { r.Params = append(r.Params, f) } } if n.Kind == "exprfield" { f.Expr = n.Children[0] } } // Core's first XML byte is header byte 1; extensions have no data byte. if first && m.ExtName == "" { if f.Size != 1 { return nil, n.fail("core request first member must occupy header data byte") } if f.Kind != "pad" { copy := f r.DataByte = © } first = false continue } first = false if f.Kind == "list" { dynamic = true } if dynamic { f.Off = -1 } if f.Kind == "pad" && n.Attr["align"] != "" { f.Align, _ = strconv.Atoi(n.Attr["align"]) } r.Fields = append(r.Fields, f) off += f.Size } r.Fixed = off - 4 out = append(out, r) } return out, nil } func requestDecl(m *Module, r Request) (string, error) { var b strings.Builder if r.Values != nil { b.WriteString(valuesDecl(r.Name, r.Values)) } fmt.Fprintf(&b, "func %s(b *xturbo.Buf", r.Name) for _, p := range r.Params { fmt.Fprintf(&b, ", %s %s", p.Param, p.GoType) } b.WriteString(") xturbo.Seq {\n") ref := func(s string) string { // xcb-proto permits an exprfield to refer to a trailing list's // implicit _len (QueryTextExtents.string_len). declared := false for _, n := range r.Node.Children { if n.Kind == "field" && n.Attr["name"] == s { declared = true } } for _, list := range r.Lists { if !declared && s == list.Node.Attr["name"]+"_len" { return "len(" + list.Param + ")" } } if list := r.Derived[s]; list != "" { return "len(" + list + ")" } return "int(" + paramName(s) + ")" } // Derived lengths are cast only after checking their wire width. for _, f := range append(append([]Field{}, r.Fields...), r.Params...) { if f.Kind != "field" || r.Derived[f.Node.Attr["name"]] == "" { continue } if f.Type.Size < 8 { fmt.Fprintf(&b, "if uint64(%s)>%d { b.Fail(xturbo.ErrBadLength); return 0 };", ref(f.Node.Attr["name"]), uint64(1)<<(8*f.Type.Size)-1) } } if r.DataByte != nil { f := r.DataByte if list := r.Derived[f.Node.Attr["name"]]; list != "" { fmt.Fprintf(&b, "if len(%s)>255 { b.Fail(xturbo.ErrBadLength); return 0 };", list) } } for _, f := range r.Lists { if f.Expr != nil && !(f.Expr.Kind == "fieldref" && r.Derived[f.Expr.Text] != "") { ex, err := renderExpr(f.Expr, ref, m) if err != nil { return "", err } fmt.Fprintf(&b, "if len(%s)!=%s { b.Fail(xturbo.ErrBadLength); return 0 };", f.Param, ex) } } body := fmt.Sprint((r.Fixed + 3) &^ 3) if len(r.Lists) > 0 { b.WriteString("size:=0;") for _, f := range r.Fields { if f.Kind == "list" { if f.Type.Variable { fmt.Fprintf(&b, "for i:=range %s { size+=%s[i].Size() };", f.Param, f.Param) } else { fmt.Fprintf(&b, "size+=len(%s)*%d;", f.Param, f.Type.Size) } } else if f.Align > 0 { fmt.Fprintf(&b, "size=(size+%d)&^%d;", f.Align-1, f.Align-1) } else { fmt.Fprintf(&b, "size+=%d;", f.Size) } } b.WriteString("size=(size+3)&^3;") body = "size" } if r.Values != nil { body += "+4*bits.OnesCount32(uint32(v.Mask))" } major := fmt.Sprint(r.Opcode) data := "0" if m.ExtName != "" { fmt.Fprintf(&b, "if b.Ext==nil { b.Fail(xturbo.ErrExtMissing); return 0 }; major:=b.Ext.Major(xturbo.Ext%s); if major==0 { b.Fail(xturbo.ErrExtMissing); return 0 };\n", m.ExtName) major = "major" data = fmt.Sprint(r.Opcode) } else if r.DataByte != nil { f := r.DataByte if f.Type.Base == "bool" { value := f.Param if f.Kind == "exprfield" { ex, err := renderExpr(f.Expr, ref, m) if err != nil { return "", err } value = "(" + ex + " != 0)" } fmt.Fprintf(&b, "var data uint8; if %s { data=1 };", value) data = "data" } else { data = "uint8(" + ref(f.Node.Attr["name"]) + ")" } } fmt.Fprintf(&b, "p,seq:=b.Request(%s,%s,%s); if p==nil { return 0 };\n", major, data, body) if len(r.Lists) > 0 { b.WriteString("off:=0;") } for _, f := range r.Fields { if f.Kind == "pad" { if len(r.Lists) > 0 { if f.Align > 0 { fmt.Fprintf(&b, "off=(off+%d)&^%d;", f.Align-1, f.Align-1) } else { fmt.Fprintf(&b, "off+=%d;", f.Size) } } continue } if f.Kind == "list" { if f.GoType == "*[32]byte" { fmt.Fprintf(&b, "copy(p[off:],%s[:]);", f.Param) } else if f.GoType == "string" || f.GoType == "[]byte" { fmt.Fprintf(&b, "copy(p[off:],%s); off+=len(%s);", f.Param, f.Param) } else { fmt.Fprintf(&b, "for i:=range %s {", f.Param) if isScalar(f.Type) { b.WriteString(putScalar(f.Type, f.Param+"[i]", "p[off:]")) } else { fmt.Fprintf(&b, "%s[i].Put(p[off:]);", f.Param) } if f.Type.Variable { fmt.Fprintf(&b, "off+=%s[i].Size();", f.Param) } else { fmt.Fprintf(&b, "off+=%d;", f.Type.Size) } b.WriteString("};") } continue } src := f.Param if list := r.Derived[f.Node.Attr["name"]]; list != "" { src = "len(" + list + ")" } if f.Kind == "exprfield" { var err error src, err = renderExpr(f.Expr, ref, m) if err != nil { return "", err } } dst := fmt.Sprintf("p[%d:]", f.Off) if len(r.Lists) > 0 { dst = "p[off:]" } if isScalar(f.Type) { b.WriteString(putScalar(f.Type, src, dst)) } else { fmt.Fprintf(&b, "%s.Put(%s);", src, dst) } if len(r.Lists) > 0 { fmt.Fprintf(&b, "off+=%d;", f.Size) } } if r.Values != nil { fmt.Fprintf(&b, "off:=%d;", r.Fixed) for _, f := range r.Values.Fields { fmt.Fprintf(&b, "if v.Mask & %s != 0 {", f.Bit) b.WriteString(putScalar(f.Field.Type, "v."+f.Field.Name, "p[off:]")) b.WriteString("off+=4 };") } b.WriteString("_ = off;") } b.WriteString("return seq }\n") return b.String(), nil } func emitRequests(modules []*Module) error { for _, m := range modules { requests, err := resolveRequests(m) if err != nil { return err } data := struct { Pkg string Decls []string }{Pkg: m.Pkg} for _, r := range requests { decl, err := requestDecl(m, r) if err != nil { return err } data.Decls = append(data.Decls, decl) } all := strings.Join(data.Decls, "\n") imp := importsFor(m, all) if len(requests) > 0 { imp += "\nimport \"bnema.dev/xturbo\"" } data.Decls = append([]string{imp}, data.Decls...) if err := writeTemplate(filepath.Join(m.Pkg, "requests_generated.go"), "requests", data); err != nil { return err } } return nil }