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