Přemysl Janouch
3e9ed4eac6
Most of XCB documentation now ends up in Go sources, although the end result is of mixed quality.
499 lines
12 KiB
Go
499 lines
12 KiB
Go
package main
|
|
|
|
/*
|
|
translation.go provides a 'Translate' method on every XML type that converts
|
|
the XML type into our "better" representation.
|
|
|
|
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
|
|
the types that encoding/xml forces us into.
|
|
*/
|
|
|
|
import (
|
|
"log"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func (xml *XML) Translate(parent *Protocol) *Protocol {
|
|
protocol := &Protocol{
|
|
Parent: parent,
|
|
Name: xml.Header,
|
|
ExtXName: xml.ExtensionXName,
|
|
ExtName: xml.ExtensionName,
|
|
MajorVersion: xml.MajorVersion,
|
|
MinorVersion: xml.MinorVersion,
|
|
|
|
Imports: make([]*Protocol, 0),
|
|
Types: make([]Type, 0),
|
|
Requests: make([]*Request, len(xml.Requests)),
|
|
}
|
|
|
|
for _, imp := range xml.Imports {
|
|
if imp.xml != nil {
|
|
protocol.Imports = append(protocol.Imports,
|
|
imp.xml.Translate(protocol))
|
|
}
|
|
}
|
|
|
|
for xmlName, srcName := range BaseTypeMap {
|
|
newBaseType := &Base{
|
|
srcName: srcName,
|
|
xmlName: xmlName,
|
|
size: newFixedSize(BaseTypeSizes[xmlName], true),
|
|
}
|
|
protocol.Types = append(protocol.Types, newBaseType)
|
|
}
|
|
for _, enum := range xml.Enums {
|
|
protocol.Types = append(protocol.Types, enum.Translate())
|
|
}
|
|
for _, xid := range xml.Xids {
|
|
protocol.Types = append(protocol.Types, xid.Translate())
|
|
}
|
|
for _, xidunion := range xml.XidUnions {
|
|
protocol.Types = append(protocol.Types, xidunion.Translate())
|
|
}
|
|
for _, typedef := range xml.TypeDefs {
|
|
protocol.Types = append(protocol.Types, typedef.Translate())
|
|
}
|
|
for _, s := range xml.Structs {
|
|
protocol.Types = append(protocol.Types, s.Translate())
|
|
}
|
|
for _, union := range xml.Unions {
|
|
protocol.Types = append(protocol.Types, union.Translate())
|
|
}
|
|
for _, ev := range xml.Events {
|
|
protocol.Types = append(protocol.Types, ev.Translate())
|
|
}
|
|
for _, evcopy := range xml.EventCopies {
|
|
protocol.Types = append(protocol.Types, evcopy.Translate())
|
|
}
|
|
for _, err := range xml.Errors {
|
|
protocol.Types = append(protocol.Types, err.Translate())
|
|
}
|
|
for _, errcopy := range xml.ErrorCopies {
|
|
protocol.Types = append(protocol.Types, errcopy.Translate())
|
|
}
|
|
|
|
for i, request := range xml.Requests {
|
|
protocol.Requests[i] = request.Translate()
|
|
}
|
|
|
|
// Now load all of the type and source name information.
|
|
protocol.Initialize()
|
|
|
|
// Make sure all enums have concrete values.
|
|
for _, typ := range protocol.Types {
|
|
enum, ok := typ.(*Enum)
|
|
if !ok {
|
|
continue
|
|
}
|
|
nextValue := 0
|
|
for _, item := range enum.Items {
|
|
if item.Expr == nil {
|
|
item.Expr = &Value{v: nextValue}
|
|
nextValue++
|
|
} else {
|
|
nextValue = item.Expr.Eval() + 1
|
|
}
|
|
}
|
|
}
|
|
|
|
return protocol
|
|
}
|
|
|
|
func (x *XMLEnum) Translate() *Enum {
|
|
enum := &Enum{
|
|
xmlName: x.Name,
|
|
Items: make([]*EnumItem, len(x.Items)),
|
|
}
|
|
for i, item := range x.Items {
|
|
enum.Items[i] = &EnumItem{
|
|
xmlName: item.Name,
|
|
Expr: item.Expr.Translate(),
|
|
}
|
|
}
|
|
return enum
|
|
}
|
|
|
|
func (x *XMLXid) Translate() *Resource {
|
|
return &Resource{
|
|
xmlName: x.Name,
|
|
}
|
|
}
|
|
|
|
func (x *XMLTypeDef) Translate() *TypeDef {
|
|
return &TypeDef{
|
|
xmlName: x.New,
|
|
Old: newTranslation(x.Old),
|
|
}
|
|
}
|
|
|
|
func (x *XMLEvent) Translate() *Event {
|
|
ev := &Event{
|
|
xmlName: x.Name,
|
|
Number: x.Number,
|
|
NoSequence: x.NoSequence,
|
|
Fields: make([]Field, 0, len(x.Fields)),
|
|
Doc: x.Doc.Translate(),
|
|
}
|
|
for _, field := range x.Fields {
|
|
ev.Fields = append(ev.Fields, field.Translate(ev, &ev.Doc))
|
|
}
|
|
return ev
|
|
}
|
|
|
|
func (x *XMLEventCopy) Translate() *EventCopy {
|
|
return &EventCopy{
|
|
xmlName: x.Name,
|
|
Number: x.Number,
|
|
Old: newTranslation(x.Ref),
|
|
}
|
|
}
|
|
|
|
func (x *XMLError) Translate() *Error {
|
|
err := &Error{
|
|
xmlName: x.Name,
|
|
Number: x.Number,
|
|
Fields: make([]Field, len(x.Fields)),
|
|
}
|
|
for i, field := range x.Fields {
|
|
err.Fields[i] = field.Translate(err, nil)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (x *XMLErrorCopy) Translate() *ErrorCopy {
|
|
return &ErrorCopy{
|
|
xmlName: x.Name,
|
|
Number: x.Number,
|
|
Old: newTranslation(x.Ref),
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
s := &Struct{
|
|
xmlName: x.Name,
|
|
Fields: make([]Field, len(x.Fields)),
|
|
}
|
|
for i, field := range x.Fields {
|
|
s.Fields[i] = field.Translate(s, nil)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (x *XMLUnion) Translate() *Union {
|
|
u := &Union{
|
|
xmlName: x.Name,
|
|
Fields: make([]Field, len(x.Fields)),
|
|
}
|
|
for i, field := range x.Fields {
|
|
u.Fields[i] = field.Translate(u, nil)
|
|
}
|
|
return u
|
|
}
|
|
|
|
func (x *XMLRequest) Translate() *Request {
|
|
r := &Request{
|
|
xmlName: x.Name,
|
|
Opcode: x.Opcode,
|
|
Combine: x.Combine,
|
|
Fields: make([]Field, 0, len(x.Fields)),
|
|
Reply: x.Reply.Translate(),
|
|
Doc: x.Doc.Translate(),
|
|
}
|
|
for _, field := range x.Fields {
|
|
if field.XMLName.Local == "fd" {
|
|
continue
|
|
}
|
|
r.Fields = append(r.Fields, field.Translate(r, &r.Doc))
|
|
}
|
|
|
|
// Address bug (or legacy code) in QueryTextExtents.
|
|
// The XML protocol description references 'string_len' in the
|
|
// computation of the 'odd_length' field. However, 'string_len' is not
|
|
// defined. Therefore, let's forcefully add it as a 'local field'.
|
|
// (i.e., a parameter in the caller but does not get sent over the wire.)
|
|
if x.Name == "QueryTextExtents" {
|
|
stringLenLocal := &LocalField{&SingleField{
|
|
xmlName: "string_len",
|
|
Type: newTranslation("CARD16"),
|
|
}}
|
|
r.Fields = append(r.Fields, stringLenLocal)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (x *XMLReply) Translate() *Reply {
|
|
if x == nil {
|
|
return nil
|
|
}
|
|
|
|
r := &Reply{
|
|
Fields: make([]Field, 0, len(x.Fields)),
|
|
Doc: x.Doc.Translate(),
|
|
}
|
|
for _, field := range x.Fields {
|
|
if field.XMLName.Local == "fd" {
|
|
continue
|
|
}
|
|
r.Fields = append(r.Fields, field.Translate(r, &r.Doc))
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (x *XMLExpression) Translate() Expression {
|
|
if x == nil {
|
|
return nil
|
|
}
|
|
|
|
switch x.XMLName.Local {
|
|
case "op":
|
|
if len(x.Exprs) != 2 {
|
|
log.Panicf("'op' found %d expressions; expected 2.", len(x.Exprs))
|
|
}
|
|
return &BinaryOp{
|
|
Op: x.Op,
|
|
Expr1: x.Exprs[0].Translate(),
|
|
Expr2: x.Exprs[1].Translate(),
|
|
}
|
|
case "unop":
|
|
if len(x.Exprs) != 1 {
|
|
log.Panicf("'unop' found %d expressions; expected 1.", len(x.Exprs))
|
|
}
|
|
return &UnaryOp{
|
|
Op: x.Op,
|
|
Expr: x.Exprs[0].Translate(),
|
|
}
|
|
case "popcount":
|
|
if len(x.Exprs) != 1 {
|
|
log.Panicf("'popcount' found %d expressions; expected 1.",
|
|
len(x.Exprs))
|
|
}
|
|
return &PopCount{
|
|
Expr: x.Exprs[0].Translate(),
|
|
}
|
|
case "value":
|
|
val, err := strconv.Atoi(strings.TrimSpace(x.Data))
|
|
if err != nil {
|
|
log.Panicf("Could not convert '%s' in 'value' expression to int.",
|
|
x.Data)
|
|
}
|
|
return &Value{
|
|
v: val,
|
|
}
|
|
case "bit":
|
|
bit, err := strconv.Atoi(strings.TrimSpace(x.Data))
|
|
if err != nil {
|
|
log.Panicf("Could not convert '%s' in 'bit' expression to int.",
|
|
x.Data)
|
|
}
|
|
if bit < 0 || bit > 31 {
|
|
log.Panicf("A 'bit' literal must be in the range [0, 31], but "+
|
|
" is %d", bit)
|
|
}
|
|
return &Bit{
|
|
b: bit,
|
|
}
|
|
case "fieldref":
|
|
return &FieldRef{
|
|
Name: x.Data,
|
|
}
|
|
case "enumref":
|
|
return &EnumRef{
|
|
EnumKind: newTranslation(x.Ref),
|
|
EnumItem: x.Data,
|
|
}
|
|
case "sumof":
|
|
return &SumOf{
|
|
Name: x.Ref,
|
|
}
|
|
}
|
|
|
|
log.Panicf("Unrecognized tag '%s' in expression context. Expected one of "+
|
|
"op, fieldref, value, bit, enumref, unop, sumof or popcount.",
|
|
x.XMLName.Local)
|
|
panic("unreachable")
|
|
}
|
|
|
|
func (x *XMLField) Translate(parent interface{}, doc *Doc) Field {
|
|
switch x.XMLName.Local {
|
|
case "pad":
|
|
return &PadField{
|
|
Bytes: x.Bytes,
|
|
Align: x.Align,
|
|
}
|
|
case "field":
|
|
s := &SingleField{
|
|
xmlName: x.Name,
|
|
Type: newTranslation(x.Type),
|
|
Comment: doc.DescribeField(x.Name),
|
|
}
|
|
return s
|
|
case "list":
|
|
return &ListField{
|
|
xmlName: x.Name,
|
|
Type: newTranslation(x.Type),
|
|
LengthExpr: x.Expr.Translate(),
|
|
}
|
|
case "localfield":
|
|
return &LocalField{&SingleField{
|
|
xmlName: x.Name,
|
|
Type: newTranslation(x.Type),
|
|
}}
|
|
case "exprfield":
|
|
return &ExprField{
|
|
xmlName: x.Name,
|
|
Type: newTranslation(x.Type),
|
|
Expr: x.Expr.Translate(),
|
|
}
|
|
case "valueparam":
|
|
return &ValueField{
|
|
Parent: parent,
|
|
MaskType: newTranslation(x.ValueMaskType),
|
|
MaskName: x.ValueMaskName,
|
|
ListName: x.ValueListName,
|
|
MaskComment: doc.DescribeField(x.ValueMaskName),
|
|
ListComment: doc.DescribeField(x.ValueListName),
|
|
}
|
|
case "switch":
|
|
swtch := &SwitchField{
|
|
Name: x.Name,
|
|
Expr: x.Expr.Translate(),
|
|
Bitcases: make([]*Bitcase, len(x.Bitcases)),
|
|
Comment: doc.DescribeField(x.Name),
|
|
}
|
|
for i, bitcase := range x.Bitcases {
|
|
swtch.Bitcases[i] = bitcase.Translate()
|
|
}
|
|
return swtch
|
|
case "required_start_align":
|
|
return &RequiredStartAlign{}
|
|
}
|
|
|
|
log.Panicf("Unrecognized field element: %s", x.XMLName.Local)
|
|
panic("unreachable")
|
|
}
|
|
|
|
func (x *XMLBitcase) Translate() *Bitcase {
|
|
b := &Bitcase{
|
|
Expr: x.Expr().Translate(),
|
|
Fields: make([]Field, len(x.Fields)),
|
|
}
|
|
for i, field := range x.Fields {
|
|
b.Fields[i] = field.Translate(b, nil)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// SrcName is used to translate any identifier into a Go name.
|
|
// Mostly used for fields, but used in a couple other places too (enum items).
|
|
func SrcName(p *Protocol, name string) string {
|
|
// If it's in the name map, use that translation.
|
|
if newn, ok := NameMap[name]; ok {
|
|
return newn
|
|
}
|
|
return splitAndTitle(name)
|
|
}
|
|
|
|
func TypeSrcName(p *Protocol, typ Type) string {
|
|
t := typ.XmlName()
|
|
|
|
// If this is a base type, then write the raw Go type.
|
|
if baseType, ok := typ.(*Base); ok {
|
|
return baseType.SrcName()
|
|
}
|
|
|
|
// If it's in the type map, use that translation.
|
|
if newt, ok := TypeMap[t]; ok {
|
|
return newt
|
|
}
|
|
|
|
// If there's a namespace to this type, just use it and be done.
|
|
if colon := strings.Index(t, ":"); colon > -1 {
|
|
namespace := t[:colon]
|
|
rest := t[colon+1:]
|
|
return p.ProtocolFind(namespace).PkgName() + "." + splitAndTitle(rest)
|
|
}
|
|
|
|
// Since there's no namespace, we're left with the raw type name.
|
|
// If the type is part of the source we're generating (i.e., there is
|
|
// no parent protocol), then just return that type name.
|
|
// Otherwise, we must qualify it with a package name.
|
|
if p.Parent == nil {
|
|
return splitAndTitle(t)
|
|
}
|
|
return p.PkgName() + "." + splitAndTitle(t)
|
|
}
|