haven/nexgb/xgbgen/translation.go
Přemysl Janouch 3e9ed4eac6
xgbgen: process <doc> elements
Most of XCB documentation now ends up in Go sources,
although the end result is of mixed quality.
2018-09-30 17:34:26 +02:00

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)
}