xgbgen: process <doc> elements

Most of XCB documentation now ends up in Go sources,
although the end result is of mixed quality.
This commit is contained in:
Přemysl Eric Janouch 2018-09-29 21:42:23 +02:00
parent 0056720d05
commit 3e9ed4eac6
Signed by: p
GPG Key ID: A0420B94F92B9493
9 changed files with 187 additions and 24 deletions

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"log" "log"
"sort" "sort"
"strings"
) )
// Context represents the protocol we're converting to Go, and a writer // Context represents the protocol we're converting to Go, and a writer
@ -34,6 +35,13 @@ func (c *Context) Put(format string, v ...interface{}) {
} }
} }
// PutComment writes each line of comment commented out to 'out'.
func (c *Context) PutComment(comment string) {
for _, line := range strings.Split(comment, "\n") {
c.Putln("// %s", line)
}
}
// Morph is the big daddy of them all. It takes in an XML byte slice, // Morph is the big daddy of them all. It takes in an XML byte slice,
// parse it, transforms the XML types into more usable types, // parse it, transforms the XML types into more usable types,
// and writes Go code to the 'out' buffer. // and writes Go code to the 'out' buffer.

View File

@ -105,6 +105,7 @@ type SingleField struct {
srcName string srcName string
xmlName string xmlName string
Type Type Type Type
Comment string
} }
func (f *SingleField) Initialize(p *Protocol) { func (f *SingleField) Initialize(p *Protocol) {
@ -255,10 +256,12 @@ func (f *ExprField) Initialize(p *Protocol) {
// integers. The mask specifies which kinds of values are in the list. // integers. The mask specifies which kinds of values are in the list.
// (i.e., See ConfigureWindow, CreateWindow, ChangeWindowAttributes, etc.) // (i.e., See ConfigureWindow, CreateWindow, ChangeWindowAttributes, etc.)
type ValueField struct { type ValueField struct {
Parent interface{} Parent interface{}
MaskType Type MaskType Type
MaskName string MaskName string
ListName string ListName string
MaskComment string
ListComment string
} }
func (f *ValueField) SrcName() string { func (f *ValueField) SrcName() string {
@ -324,6 +327,7 @@ type SwitchField struct {
MaskName string MaskName string
Expr Expression Expr Expression
Bitcases []*Bitcase Bitcases []*Bitcase
Comment string
} }
func (f *SwitchField) SrcName() string { func (f *SwitchField) SrcName() string {

View File

@ -168,7 +168,13 @@ func (f *ExprField) Write(c *Context, prefix string) {
// Value field // Value field
func (f *ValueField) Define(c *Context) { func (f *ValueField) Define(c *Context) {
if f.MaskComment != "" {
c.PutComment(f.MaskComment)
}
c.Putln("%s %s", f.MaskName, f.SrcType()) c.Putln("%s %s", f.MaskName, f.SrcType())
if f.ListComment != "" {
c.PutComment(f.ListComment)
}
c.Putln("%s []uint32", f.ListName) c.Putln("%s []uint32", f.ListName)
} }
@ -197,6 +203,9 @@ func (f *ValueField) Write(c *Context, prefix string) {
// Switch field // Switch field
func (f *SwitchField) Define(c *Context) { func (f *SwitchField) Define(c *Context) {
if f.Comment != "" {
c.PutComment(f.Comment)
}
c.Putln("%s []uint32", f.Name) c.Putln("%s []uint32", f.Name)
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
) )
@ -12,6 +13,33 @@ func (r *Request) Define(c *Context) {
c.Putln("*xgb.Cookie") c.Putln("*xgb.Cookie")
c.Putln("}") c.Putln("}")
c.Putln("") c.Putln("")
if r.Doc.Description != "" {
c.PutComment(r.Doc.Description)
c.Putln("//")
}
allErrors := make([]string, 0, len(r.Doc.Errors))
for kind := range r.Doc.Errors {
allErrors = append(allErrors, kind)
}
sort.Strings(allErrors)
undocErrors := make([]string, 0)
for _, kind := range allErrors {
if desc := r.Doc.Errors[kind]; desc == "" {
undocErrors = append(undocErrors, kind)
} else {
c.PutComment(fmt.Sprintf("May return a %s error if %s%s", kind,
strings.ToLower(desc[:1]), desc[1:]))
c.Putln("//")
}
}
if len(undocErrors) > 0 {
c.Putln("// May return %s errors.", strings.Join(undocErrors, ", "))
c.Putln("//")
}
if r.Reply != nil { if r.Reply != nil {
c.Putln("// %s sends a checked request.", r.SrcName()) c.Putln("// %s sends a checked request.", r.SrcName())
c.Putln("// If an error occurs, it will be returned with the reply "+ c.Putln("// If an error occurs, it will be returned with the reply "+

View File

@ -6,6 +6,9 @@ import (
) )
func (f *SingleField) Define(c *Context) { func (f *SingleField) Define(c *Context) {
if f.Comment != "" {
c.PutComment(f.Comment)
}
c.Putln("%s %s", f.SrcName(), f.Type.SrcName()) c.Putln("%s %s", f.SrcName(), f.Type.SrcName())
} }

View File

@ -6,6 +6,22 @@ import (
"unicode" "unicode"
) )
// Doc contains any documentation, if present. Example C code is excluded.
type Doc struct {
Brief string // short description
Description string // long description
Fields map[string]string // from field name to description
Errors map[string]string // from error type to description
}
// DescribeField is an accessor that supports nil receivers.
func (d *Doc) DescribeField(name string) string {
if d == nil {
return ""
}
return d.Fields[name]
}
// Request represents all XML 'request' nodes. // Request represents all XML 'request' nodes.
// If the request doesn't have a reply, Reply is nil. // If the request doesn't have a reply, Reply is nil.
type Request struct { type Request struct {
@ -15,6 +31,7 @@ type Request struct {
Combine bool // Not currently used. Combine bool // Not currently used.
Fields []Field // All fields in the request. Fields []Field // All fields in the request.
Reply *Reply // A reply, if one exists for this request. Reply *Reply // A reply, if one exists for this request.
Doc Doc // Documentation.
} }
type Requests []*Request type Requests []*Request
@ -126,6 +143,7 @@ func (r *Request) Size(c *Context) Size {
// Reply encapsulates the fields associated with a 'reply' element. // Reply encapsulates the fields associated with a 'reply' element.
type Reply struct { type Reply struct {
Fields []Field Fields []Field
Doc Doc
} }
// Size gets the number of bytes in this request's reply. // Size gets the number of bytes in this request's reply.

View File

@ -7,13 +7,11 @@ the XML type into our "better" representation.
i.e., the representation of Fields and Expressions is just too general. i.e., the representation of Fields and Expressions is just too general.
We end up losing a lot of the advantages of static typing if we keep We end up losing a lot of the advantages of static typing if we keep
the types that encoding/xml forces us into. the types that encoding/xml forces us into.
Please see 'representation.go' for the type definitions that we're
translating to.
*/ */
import ( import (
"log" "log"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
@ -138,12 +136,10 @@ func (x *XMLEvent) Translate() *Event {
Number: x.Number, Number: x.Number,
NoSequence: x.NoSequence, NoSequence: x.NoSequence,
Fields: make([]Field, 0, len(x.Fields)), Fields: make([]Field, 0, len(x.Fields)),
Doc: x.Doc.Translate(),
} }
for _, field := range x.Fields { for _, field := range x.Fields {
if field.XMLName.Local == "doc" { ev.Fields = append(ev.Fields, field.Translate(ev, &ev.Doc))
continue
}
ev.Fields = append(ev.Fields, field.Translate(ev))
} }
return ev return ev
} }
@ -163,7 +159,7 @@ func (x *XMLError) Translate() *Error {
Fields: make([]Field, len(x.Fields)), Fields: make([]Field, len(x.Fields)),
} }
for i, field := range x.Fields { for i, field := range x.Fields {
err.Fields[i] = field.Translate(err) err.Fields[i] = field.Translate(err, nil)
} }
return err return err
} }
@ -176,13 +172,82 @@ func (x *XMLErrorCopy) Translate() *ErrorCopy {
} }
} }
// XCB documentation is positively stuffed with TODOs. We'd like to make it
// look a bit less shitty, so remove those as they don't convey information.
//
// ^TODO
// ^TODO:
// ^TODO: question?
// ^TODO: Some words
// ^TODO: some words
// ^TODO: some words with full stop.
// ^TODO: some words with full stop. and a question?
// ... (TODO),
// ... (TODO).
// ... (TODO: a question?).
// ... TODO: a question?
// ... (word TODO) ...
var todoRE = regexp.MustCompile(`(?m:^TODO.*| \([^)]*TODO[^)]*\)| TODO:.*)`)
var paraRE = regexp.MustCompile(`\n{3,}`)
var backticksRE = regexp.MustCompile("`(?:xcb_|XCB_)?(.*?)(?:_t)?`")
// fixDocumentation tries to translate XCB documentation to match XGB.
// Note that <doc> blocks only appear in xproto, so this doesn't have that much
// of a value--users still need to read Xlib or X11 protocol docs.
// Despite that, it's better to have something than nothing.
//
// We don't attempt to add proper prefixes to enum values or guess at
// specific XCB_NONE types (the latter is undecidable).
//
// We can't decide whether `fields_len` should be converted to len(Fields)
// or FieldsLen because e.g. StringLen is retained in ImageText8/16 but
// PointsLen is implied by the length of the Points slice in PolyLine.
func fixDocumentation(xcb string) string {
last, result := 0, make([]byte, 0, len(xcb))
for _, m := range backticksRE.FindAllStringSubmatchIndex(xcb, -1) {
result = append(result, xcb[last:m[0]]...)
inner := xcb[m[2]:m[3]]
last = m[1]
// Do not convert atom names to identifiers, mainly _NET_WM_*.
if strings.Contains(inner, "WM_") {
result = append(result, inner...)
} else {
result = append(result, splitAndTitle(inner)...)
}
}
result = todoRE.ReplaceAllLiteral(append(result, xcb[last:]...), nil)
result = paraRE.ReplaceAllLiteral(result, []byte("\n\n"))
return strings.TrimSpace(string(result))
}
func (x *XMLDoc) Translate() Doc {
if x == nil {
return Doc{}
}
d := Doc{
Brief: fixDocumentation(x.Brief),
Description: fixDocumentation(x.Description),
Fields: make(map[string]string),
Errors: make(map[string]string),
}
for _, x := range x.Fields {
d.Fields[x.Name] = fixDocumentation(x.Description)
}
for _, x := range x.Errors {
d.Errors[x.Type] = fixDocumentation(x.Description)
}
return d
}
func (x *XMLStruct) Translate() *Struct { func (x *XMLStruct) Translate() *Struct {
s := &Struct{ s := &Struct{
xmlName: x.Name, xmlName: x.Name,
Fields: make([]Field, len(x.Fields)), Fields: make([]Field, len(x.Fields)),
} }
for i, field := range x.Fields { for i, field := range x.Fields {
s.Fields[i] = field.Translate(s) s.Fields[i] = field.Translate(s, nil)
} }
return s return s
} }
@ -193,7 +258,7 @@ func (x *XMLUnion) Translate() *Union {
Fields: make([]Field, len(x.Fields)), Fields: make([]Field, len(x.Fields)),
} }
for i, field := range x.Fields { for i, field := range x.Fields {
u.Fields[i] = field.Translate(u) u.Fields[i] = field.Translate(u, nil)
} }
return u return u
} }
@ -205,12 +270,13 @@ func (x *XMLRequest) Translate() *Request {
Combine: x.Combine, Combine: x.Combine,
Fields: make([]Field, 0, len(x.Fields)), Fields: make([]Field, 0, len(x.Fields)),
Reply: x.Reply.Translate(), Reply: x.Reply.Translate(),
Doc: x.Doc.Translate(),
} }
for _, field := range x.Fields { for _, field := range x.Fields {
if field.XMLName.Local == "doc" || field.XMLName.Local == "fd" { if field.XMLName.Local == "fd" {
continue continue
} }
r.Fields = append(r.Fields, field.Translate(r)) r.Fields = append(r.Fields, field.Translate(r, &r.Doc))
} }
// Address bug (or legacy code) in QueryTextExtents. // Address bug (or legacy code) in QueryTextExtents.
@ -236,12 +302,13 @@ func (x *XMLReply) Translate() *Reply {
r := &Reply{ r := &Reply{
Fields: make([]Field, 0, len(x.Fields)), Fields: make([]Field, 0, len(x.Fields)),
Doc: x.Doc.Translate(),
} }
for _, field := range x.Fields { for _, field := range x.Fields {
if field.XMLName.Local == "doc" || field.XMLName.Local == "fd" { if field.XMLName.Local == "fd" {
continue continue
} }
r.Fields = append(r.Fields, field.Translate(r)) r.Fields = append(r.Fields, field.Translate(r, &r.Doc))
} }
return r return r
} }
@ -320,7 +387,7 @@ func (x *XMLExpression) Translate() Expression {
panic("unreachable") panic("unreachable")
} }
func (x *XMLField) Translate(parent interface{}) Field { func (x *XMLField) Translate(parent interface{}, doc *Doc) Field {
switch x.XMLName.Local { switch x.XMLName.Local {
case "pad": case "pad":
return &PadField{ return &PadField{
@ -331,6 +398,7 @@ func (x *XMLField) Translate(parent interface{}) Field {
s := &SingleField{ s := &SingleField{
xmlName: x.Name, xmlName: x.Name,
Type: newTranslation(x.Type), Type: newTranslation(x.Type),
Comment: doc.DescribeField(x.Name),
} }
return s return s
case "list": case "list":
@ -352,16 +420,19 @@ func (x *XMLField) Translate(parent interface{}) Field {
} }
case "valueparam": case "valueparam":
return &ValueField{ return &ValueField{
Parent: parent, Parent: parent,
MaskType: newTranslation(x.ValueMaskType), MaskType: newTranslation(x.ValueMaskType),
MaskName: x.ValueMaskName, MaskName: x.ValueMaskName,
ListName: x.ValueListName, ListName: x.ValueListName,
MaskComment: doc.DescribeField(x.ValueMaskName),
ListComment: doc.DescribeField(x.ValueListName),
} }
case "switch": case "switch":
swtch := &SwitchField{ swtch := &SwitchField{
Name: x.Name, Name: x.Name,
Expr: x.Expr.Translate(), Expr: x.Expr.Translate(),
Bitcases: make([]*Bitcase, len(x.Bitcases)), Bitcases: make([]*Bitcase, len(x.Bitcases)),
Comment: doc.DescribeField(x.Name),
} }
for i, bitcase := range x.Bitcases { for i, bitcase := range x.Bitcases {
swtch.Bitcases[i] = bitcase.Translate() swtch.Bitcases[i] = bitcase.Translate()
@ -381,7 +452,7 @@ func (x *XMLBitcase) Translate() *Bitcase {
Fields: make([]Field, len(x.Fields)), Fields: make([]Field, len(x.Fields)),
} }
for i, field := range x.Fields { for i, field := range x.Fields {
b.Fields[i] = field.Translate(b) b.Fields[i] = field.Translate(b, nil)
} }
return b return b
} }

View File

@ -190,6 +190,7 @@ type Event struct {
Number int Number int
NoSequence bool NoSequence bool
Fields []Field Fields []Field
Doc Doc
} }
func (e *Event) SrcName() string { func (e *Event) SrcName() string {

View File

@ -92,6 +92,24 @@ type XMLErrorCopy struct {
Ref string `xml:"ref,attr"` Ref string `xml:"ref,attr"`
} }
type XMLDocField struct {
Name string `xml:"name,attr"`
Description string `xml:",chardata"`
}
type XMLDocError struct {
Type string `xml:"type,attr"`
Description string `xml:",chardata"`
}
type XMLDoc struct {
Brief string `xml:"brief"`
Description string `xml:"description"`
Example string `xml:"example"`
Fields []*XMLDocField `xml:"field"`
Errors []*XMLDocError `xml:"error"`
}
type XMLStruct struct { type XMLStruct struct {
Name string `xml:"name,attr"` Name string `xml:"name,attr"`
Fields []*XMLField `xml:",any"` Fields []*XMLField `xml:",any"`
@ -108,10 +126,12 @@ type XMLRequest struct {
Combine bool `xml:"combine-adjacent,attr"` Combine bool `xml:"combine-adjacent,attr"`
Fields []*XMLField `xml:",any"` Fields []*XMLField `xml:",any"`
Reply *XMLReply `xml:"reply"` Reply *XMLReply `xml:"reply"`
Doc *XMLDoc `xml:"doc"`
} }
type XMLReply struct { type XMLReply struct {
Fields []*XMLField `xml:",any"` Fields []*XMLField `xml:",any"`
Doc *XMLDoc `xml:"doc"`
} }
type XMLEvent struct { type XMLEvent struct {
@ -119,6 +139,7 @@ type XMLEvent struct {
Number int `xml:"number,attr"` Number int `xml:"number,attr"`
NoSequence bool `xml:"no-sequence-number,attr"` NoSequence bool `xml:"no-sequence-number,attr"`
Fields []*XMLField `xml:",any"` Fields []*XMLField `xml:",any"`
Doc *XMLDoc `xml:"doc"`
} }
type XMLError struct { type XMLError struct {