diff options
Diffstat (limited to 'nexgb/xgbgen')
-rw-r--r-- | nexgb/xgbgen/COPYING | 13 | ||||
-rw-r--r-- | nexgb/xgbgen/context.go | 168 | ||||
-rw-r--r-- | nexgb/xgbgen/doc.go | 74 | ||||
-rw-r--r-- | nexgb/xgbgen/expression.go | 436 | ||||
-rw-r--r-- | nexgb/xgbgen/field.go | 398 | ||||
-rw-r--r-- | nexgb/xgbgen/go.go | 220 | ||||
-rw-r--r-- | nexgb/xgbgen/go_error.go | 179 | ||||
-rw-r--r-- | nexgb/xgbgen/go_event.go | 209 | ||||
-rw-r--r-- | nexgb/xgbgen/go_list.go | 107 | ||||
-rw-r--r-- | nexgb/xgbgen/go_request_reply.go | 242 | ||||
-rw-r--r-- | nexgb/xgbgen/go_single_field.go | 166 | ||||
-rw-r--r-- | nexgb/xgbgen/go_struct.go | 118 | ||||
-rw-r--r-- | nexgb/xgbgen/go_union.go | 147 | ||||
-rw-r--r-- | nexgb/xgbgen/main.go | 64 | ||||
-rw-r--r-- | nexgb/xgbgen/misc.go | 49 | ||||
-rw-r--r-- | nexgb/xgbgen/protocol.go | 70 | ||||
-rw-r--r-- | nexgb/xgbgen/request_reply.go | 152 | ||||
-rw-r--r-- | nexgb/xgbgen/size.go | 31 | ||||
-rw-r--r-- | nexgb/xgbgen/translation.go | 427 | ||||
-rw-r--r-- | nexgb/xgbgen/type.go | 390 | ||||
-rw-r--r-- | nexgb/xgbgen/xml.go | 138 | ||||
-rw-r--r-- | nexgb/xgbgen/xml_fields.go | 86 |
22 files changed, 3884 insertions, 0 deletions
diff --git a/nexgb/xgbgen/COPYING b/nexgb/xgbgen/COPYING new file mode 100644 index 0000000..5c93f45 --- /dev/null +++ b/nexgb/xgbgen/COPYING @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/nexgb/xgbgen/context.go b/nexgb/xgbgen/context.go new file mode 100644 index 0000000..0728b64 --- /dev/null +++ b/nexgb/xgbgen/context.go @@ -0,0 +1,168 @@ +package main + +import ( + "bytes" + "encoding/xml" + "fmt" + "log" + "sort" +) + +// Context represents the protocol we're converting to Go, and a writer +// buffer to write the Go source to. +type Context struct { + protocol *Protocol + out *bytes.Buffer +} + +func newContext() *Context { + return &Context{ + out: bytes.NewBuffer([]byte{}), + } +} + +// Putln calls put and adds a new line to the end of 'format'. +func (c *Context) Putln(format string, v ...interface{}) { + c.Put(format+"\n", v...) +} + +// Put is a short alias to write to 'out'. +func (c *Context) Put(format string, v ...interface{}) { + _, err := c.out.WriteString(fmt.Sprintf(format, v...)) + if err != nil { + log.Fatalf("There was an error writing to context buffer: %s", err) + } +} + +// 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, +// and writes Go code to the 'out' buffer. +func (c *Context) Morph(xmlBytes []byte) { + parsedXml := &XML{} + err := xml.Unmarshal(xmlBytes, parsedXml) + if err != nil { + log.Fatal(err) + } + + // Parse all imports + parsedXml.Imports.Eval() + + // Translate XML types to nice types + c.protocol = parsedXml.Translate(nil) + + // For backwards compatibility we patch the type of the send_event field of + // PutImage to be byte + if c.protocol.Name == "shm" { + for _, req := range c.protocol.Requests { + if req.xmlName != "PutImage" { + continue + } + for _, ifield := range req.Fields { + field, ok := ifield.(*SingleField) + if !ok || field.xmlName != "send_event" { + continue + } + field.Type = &Base{ srcName: "byte", xmlName: "CARD8", size: newFixedSize(1, true) } + } + } + } + + // Start with Go header. + c.Putln("// Package %s is the X client API for the %s extension.", + c.protocol.PkgName(), c.protocol.ExtXName) + c.Putln("package %s", c.protocol.PkgName()) + c.Putln("") + c.Putln("// This file is automatically generated from %s.xml. "+ + "Edit at your peril!", c.protocol.Name) + c.Putln("") + + // Write imports. We always need to import at least xgb. + // We also need to import xproto if it's an extension. + c.Putln("import (") + c.Putln("\"github.com/BurntSushi/xgb\"") + c.Putln("") + if c.protocol.isExt() { + c.Putln("\"github.com/BurntSushi/xgb/xproto\"") + } + + sort.Sort(Protocols(c.protocol.Imports)) + for _, imp := range c.protocol.Imports { + // We always import xproto, so skip it if it's explicitly imported + if imp.Name == "xproto" { + continue + } + c.Putln("\"github.com/BurntSushi/xgb/%s\"", imp.Name) + } + c.Putln(")") + c.Putln("") + + // If this is an extension, create a function to initialize the extension + // before it can be used. + if c.protocol.isExt() { + xname := c.protocol.ExtXName + + c.Putln("// Init must be called before using the %s extension.", + xname) + c.Putln("func Init(c *xgb.Conn) error {") + c.Putln("reply, err := xproto.QueryExtension(c, %d, \"%s\").Reply()", + len(xname), xname) + c.Putln("switch {") + c.Putln("case err != nil:") + c.Putln("return err") + c.Putln("case !reply.Present:") + c.Putln("return xgb.Errorf(\"No extension named %s could be found on "+ + "on the server.\")", xname) + c.Putln("}") + c.Putln("") + c.Putln("c.ExtLock.Lock()") + c.Putln("c.Extensions[\"%s\"] = reply.MajorOpcode", xname) + c.Putln("c.ExtLock.Unlock()") + c.Putln("for evNum, fun := range xgb.NewExtEventFuncs[\"%s\"] {", + xname) + c.Putln("xgb.NewEventFuncs[int(reply.FirstEvent) + evNum] = fun") + c.Putln("}") + c.Putln("for errNum, fun := range xgb.NewExtErrorFuncs[\"%s\"] {", + xname) + c.Putln("xgb.NewErrorFuncs[int(reply.FirstError) + errNum] = fun") + c.Putln("}") + c.Putln("return nil") + c.Putln("}") + c.Putln("") + + // Make sure newExtEventFuncs["EXT_NAME"] map is initialized. + // Same deal for newExtErrorFuncs["EXT_NAME"] + c.Putln("func init() {") + c.Putln("xgb.NewExtEventFuncs[\"%s\"] = make(map[int]xgb.NewEventFun)", + xname) + c.Putln("xgb.NewExtErrorFuncs[\"%s\"] = make(map[int]xgb.NewErrorFun)", + xname) + c.Putln("}") + c.Putln("") + } else { + // In the xproto package, we must provide a Setup function that uses + // SetupBytes in xgb.Conn to return a SetupInfo structure. + c.Putln("// Setup parses the setup bytes retrieved when") + c.Putln("// connecting into a SetupInfo struct.") + c.Putln("func Setup(c *xgb.Conn) *SetupInfo {") + c.Putln("setup := new(SetupInfo)") + c.Putln("SetupInfoRead(c.SetupBytes, setup)") + c.Putln("return setup") + c.Putln("}") + c.Putln("") + c.Putln("// DefaultScreen gets the default screen info from SetupInfo.") + c.Putln("func (s *SetupInfo) DefaultScreen(c *xgb.Conn) *ScreenInfo {") + c.Putln("return &s.Roots[c.DefaultScreen]") + c.Putln("}") + c.Putln("") + } + + // Now write Go source code + sort.Sort(Types(c.protocol.Types)) + sort.Sort(Requests(c.protocol.Requests)) + for _, typ := range c.protocol.Types { + typ.Define(c) + } + for _, req := range c.protocol.Requests { + req.Define(c) + } +} diff --git a/nexgb/xgbgen/doc.go b/nexgb/xgbgen/doc.go new file mode 100644 index 0000000..4ba6145 --- /dev/null +++ b/nexgb/xgbgen/doc.go @@ -0,0 +1,74 @@ +/* +xgbgen constructs Go source files from xproto XML description files. xgbgen +accomplishes the same task as the Python code generator for XCB and xpyb. + +Usage: + xgbgen [flags] some-protocol.xml + +The flags are: + --proto-path path + The path to a directory containing xproto XML description files. + This is only necessary when 'some-protocol.xml' imports other + protocol files. + --gofmt=true + When false, the outputted Go code will not be gofmt'd. And it won't + be very pretty at all. This is typically useful if there are syntax + errors that need to be debugged in code generation. gofmt will hiccup; + this will allow you to see the raw code. + +How it works + +xgbgen works by parsing the input XML file using Go's encoding/xml package. +The majority of this work is done in xml.go and xml_fields.go, where the +appropriate types are declared. + +Due to the nature of the XML in the protocol description files, the types +required to parse the XML are not well suited to reasoning about code +generation. Because of this, all data parsed in the XML types is translated +into more reasonable types. This translation is done in translation.go, +and is mainly grunt work. (The only interesting tidbits are the translation +of XML names to Go source names, and connecting fields with their +appropriate types.) + +The organization of these types is greatly +inspired by the description of the XML found here: +http://cgit.freedesktop.org/xcb/proto/tree/doc/xml-xcb.txt + +These types come with a lot of supporting methods to make their use in +code generation easier. They can be found in expression.go, field.go, +protocol.go, request_reply.go and type.go. Of particular interest are +expression evaluation and size calculation (in bytes). + +These types also come with supporting methods that convert their +representation into Go source code. I've quartered such methods in +go.go, go_error.go, go_event.go, go_list.go, go_request_reply.go, +go_single_field.go, go_struct.go and go_union.go. The idea is to keep +as much of the Go specific code generation in one area as possible. Namely, +while not *all* Go related code is found in the 'go*.go' files, *most* +of it is. (If there's any interest in using xgbgen for other languages, +I'd be happy to try and make xgbgen a little more friendly in this regard. +I did, however, design xgbgen with this in mind, so it shouldn't involve +anything as serious as a re-design.) + +Why + +I wrote xgbgen because I found the existing code generator that was written in +Python to be unwieldy. In particular, static and strong typing greatly helped +me reason better about the code generation task. + +What does not work + +The core X protocol should be completely working. As far as I know, most +extensions should work too, although I've only tested (and not much) the +Xinerama and RandR extensions. + +XKB does not work. I don't have any real plans of working on this unless there +is demand and I have some test cases to work with. (i.e., even if I could get +something generated for XKB, I don't have the inclination to understand it +enough to verify that it works.) XKB poses several extremely difficult +problems that XCB also has trouble with. More info on that can be found at +http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_issues and +http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_internals. + +*/ +package main diff --git a/nexgb/xgbgen/expression.go b/nexgb/xgbgen/expression.go new file mode 100644 index 0000000..3e2235d --- /dev/null +++ b/nexgb/xgbgen/expression.go @@ -0,0 +1,436 @@ +package main + +import ( + "fmt" + "log" +) + +// Expression represents all the different forms of expressions possible in +// side an XML protocol description file. It's also received a few custom +// addendums to make applying special functions (like padding) easier. +type Expression interface { + // Concrete determines whether this particular expression can be computed + // to some constant value inside xgbgen. (The alternative is that the + // expression can only be computed with values at run time of the + // generated code.) + Concrete() bool + + // Eval evaluates a concrete expression. It is an error to call Eval + // on any expression that is not concrete (or contains any sub-expression + // that is not concrete). + Eval() int + + // Reduce attempts to evaluate any concrete sub-expressions. + // i.e., (1 + 2 * (5 + 1 + someSizeOfStruct) reduces to + // (3 * (6 + someSizeOfStruct)). + // 'prefix' is used preprended to any field reference name. + Reduce(prefix string) string + + // String is an alias for Reduce("") + String() string + + // Initialize makes sure all names in this expression and any subexpressions + // have been translated to Go source names. + Initialize(p *Protocol) + + // Makes all field references relative to path + Specialize(path string) Expression +} + +// Function is a custom expression not found in the XML. It's simply used +// to apply a function named in 'Name' to the Expr expression. +type Function struct { + Name string + Expr Expression +} + +func (e *Function) Concrete() bool { + return false +} + +func (e *Function) Eval() int { + log.Fatalf("Cannot evaluate a 'Function'. It is not concrete.") + panic("unreachable") +} + +func (e *Function) Reduce(prefix string) string { + return fmt.Sprintf("%s(%s)", e.Name, e.Expr.Reduce(prefix)) +} + +func (e *Function) String() string { + return e.Reduce("") +} + +func (e *Function) Initialize(p *Protocol) { + e.Expr.Initialize(p) +} + +func (e *Function) Specialize(path string) Expression { + r := *e + r.Expr = r.Expr.Specialize(path) + return &r +} + +// BinaryOp is an expression that performs some operation (defined in the XML +// file) with Expr1 and Expr2 as operands. +type BinaryOp struct { + Op string + Expr1 Expression + Expr2 Expression +} + +// newBinaryOp constructs a new binary expression when both expr1 and expr2 +// are not nil. If one or both are nil, then the non-nil expression is +// returned unchanged or nil is returned. +func newBinaryOp(op string, expr1, expr2 Expression) Expression { + switch { + case expr1 != nil && expr2 != nil: + return &BinaryOp{ + Op: op, + Expr1: expr1, + Expr2: expr2, + } + case expr1 != nil && expr2 == nil: + return expr1 + case expr1 == nil && expr2 != nil: + return expr2 + case expr1 == nil && expr2 == nil: + return nil + } + panic("unreachable") +} + +func (e *BinaryOp) Concrete() bool { + return e.Expr1.Concrete() && e.Expr2.Concrete() +} + +func (e *BinaryOp) Eval() int { + switch e.Op { + case "+": + return e.Expr1.Eval() + e.Expr2.Eval() + case "-": + return e.Expr1.Eval() - e.Expr2.Eval() + case "*": + return e.Expr1.Eval() * e.Expr2.Eval() + case "/": + return e.Expr1.Eval() / e.Expr2.Eval() + case "&": + return e.Expr1.Eval() & e.Expr2.Eval() + case "<<": + return int(uint(e.Expr1.Eval()) << uint(e.Expr2.Eval())) + } + + log.Fatalf("Invalid binary operator '%s' for expression.", e.Op) + panic("unreachable") +} + +func (e *BinaryOp) Reduce(prefix string) string { + if e.Concrete() { + return fmt.Sprintf("%d", e.Eval()) + } + + // An incredibly dirty hack to make sure any time we perform an operation + // on a field, we're dealing with ints... + expr1, expr2 := e.Expr1, e.Expr2 + switch expr1.(type) { + case *FieldRef: + expr1 = &Function{ + Name: "int", + Expr: expr1, + } + } + switch expr2.(type) { + case *FieldRef: + expr2 = &Function{ + Name: "int", + Expr: expr2, + } + } + return fmt.Sprintf("(%s %s %s)", + expr1.Reduce(prefix), e.Op, expr2.Reduce(prefix)) +} + +func (e *BinaryOp) String() string { + return e.Reduce("") +} + +func (e *BinaryOp) Initialize(p *Protocol) { + e.Expr1.Initialize(p) + e.Expr2.Initialize(p) +} + +func (e *BinaryOp) Specialize(path string) Expression { + r := *e + r.Expr1 = r.Expr1.Specialize(path) + r.Expr2 = r.Expr2.Specialize(path) + return &r +} + +// UnaryOp is the same as BinaryOp, except it's a unary operator with only +// one sub-expression. +type UnaryOp struct { + Op string + Expr Expression +} + +func (e *UnaryOp) Concrete() bool { + return e.Expr.Concrete() +} + +func (e *UnaryOp) Eval() int { + switch e.Op { + case "~": + return ^e.Expr.Eval() + } + + log.Fatalf("Invalid unary operator '%s' for expression.", e.Op) + panic("unreachable") +} + +func (e *UnaryOp) Reduce(prefix string) string { + if e.Concrete() { + return fmt.Sprintf("%d", e.Eval()) + } + return fmt.Sprintf("(%s (%s))", e.Op, e.Expr.Reduce(prefix)) +} + +func (e *UnaryOp) String() string { + return e.Reduce("") +} + +func (e *UnaryOp) Initialize(p *Protocol) { + e.Expr.Initialize(p) +} + +func (e *UnaryOp) Specialize(path string) Expression { + r := *e + r.Expr = r.Expr.Specialize(path) + return &r +} + +// Padding represents the application of the 'pad' function to some +// sub-expression. +type Padding struct { + Expr Expression +} + +func (e *Padding) Concrete() bool { + return e.Expr.Concrete() +} + +func (e *Padding) Eval() int { + return pad(e.Expr.Eval()) +} + +func (e *Padding) Reduce(prefix string) string { + if e.Concrete() { + return fmt.Sprintf("%d", e.Eval()) + } + return fmt.Sprintf("xgb.Pad(%s)", e.Expr.Reduce(prefix)) +} + +func (e *Padding) String() string { + return e.Reduce("") +} + +func (e *Padding) Initialize(p *Protocol) { + e.Expr.Initialize(p) +} + +func (e *Padding) Specialize(path string) Expression { + r := *e + r.Expr = r.Expr.Specialize(path) + return &r +} + +// PopCount represents the application of the 'PopCount' function to +// some sub-expression. +type PopCount struct { + Expr Expression +} + +func (e *PopCount) Concrete() bool { + return e.Expr.Concrete() +} + +func (e *PopCount) Eval() int { + return int(popCount(uint(e.Expr.Eval()))) +} + +func (e *PopCount) Reduce(prefix string) string { + if e.Concrete() { + return fmt.Sprintf("%d", e.Eval()) + } + return fmt.Sprintf("xgb.PopCount(%s)", e.Expr.Reduce(prefix)) +} + +func (e *PopCount) String() string { + return e.Reduce("") +} + +func (e *PopCount) Initialize(p *Protocol) { + e.Expr.Initialize(p) +} + +func (e *PopCount) Specialize(path string) Expression { + r := *e + r.Expr = r.Expr.Specialize(path) + return &r +} + +// Value represents some constant integer. +type Value struct { + v int +} + +func (e *Value) Concrete() bool { + return true +} + +func (e *Value) Eval() int { + return e.v +} + +func (e *Value) Reduce(prefix string) string { + return fmt.Sprintf("%d", e.v) +} + +func (e *Value) String() string { + return e.Reduce("") +} + +func (e *Value) Initialize(p *Protocol) {} + +func (e *Value) Specialize(path string) Expression { + return e +} + +// Bit represents some bit whose value is computed by '1 << bit'. +type Bit struct { + b int +} + +func (e *Bit) Concrete() bool { + return true +} + +func (e *Bit) Eval() int { + return int(1 << uint(e.b)) +} + +func (e *Bit) Reduce(prefix string) string { + return fmt.Sprintf("%d", e.Eval()) +} + +func (e *Bit) String() string { + return e.Reduce("") +} + +func (e *Bit) Initialize(p *Protocol) {} + +func (e *Bit) Specialize(path string) Expression { + return e +} + +// FieldRef represents a reference to some variable in the generated code +// with name Name. +type FieldRef struct { + Name string +} + +func (e *FieldRef) Concrete() bool { + return false +} + +func (e *FieldRef) Eval() int { + log.Fatalf("Cannot evaluate a 'FieldRef'. It is not concrete.") + panic("unreachable") +} + +func (e *FieldRef) Reduce(prefix string) string { + val := e.Name + if len(prefix) > 0 { + val = fmt.Sprintf("%s%s", prefix, val) + } + return val +} + +func (e *FieldRef) String() string { + return e.Reduce("") +} + +func (e *FieldRef) Initialize(p *Protocol) { + e.Name = SrcName(p, e.Name) +} + +func (e *FieldRef) Specialize(path string) Expression { + return &FieldRef{Name: path + "." + e.Name} +} + +// EnumRef represents a reference to some enumeration field. +// EnumKind is the "group" an EnumItem is the name of the specific enumeration +// value inside that group. +type EnumRef struct { + EnumKind Type + EnumItem string +} + +func (e *EnumRef) Concrete() bool { + return false +} + +func (e *EnumRef) Eval() int { + log.Fatalf("Cannot evaluate an 'EnumRef'. It is not concrete.") + panic("unreachable") +} + +func (e *EnumRef) Reduce(prefix string) string { + return fmt.Sprintf("%s%s", e.EnumKind, e.EnumItem) +} + +func (e *EnumRef) String() string { + return e.Reduce("") +} + +func (e *EnumRef) Initialize(p *Protocol) { + e.EnumKind = e.EnumKind.(*Translation).RealType(p) + e.EnumItem = SrcName(p, e.EnumItem) +} + +func (e *EnumRef) Specialize(path string) Expression { + return e +} + +// SumOf represents a summation of the variable in the generated code named by +// Name. It is not currently used. (It's XKB voodoo.) +type SumOf struct { + Name string +} + +func (e *SumOf) Concrete() bool { + return false +} + +func (e *SumOf) Eval() int { + log.Fatalf("Cannot evaluate a 'SumOf'. It is not concrete.") + panic("unreachable") +} + +func (e *SumOf) Reduce(prefix string) string { + if len(prefix) > 0 { + return fmt.Sprintf("sum(%s%s)", prefix, e.Name) + } + return fmt.Sprintf("sum(%s)", e.Name) +} + +func (e *SumOf) String() string { + return e.Reduce("") +} + +func (e *SumOf) Initialize(p *Protocol) { + e.Name = SrcName(p, e.Name) +} + +func (e *SumOf) Specialize(path string) Expression { + return e +} diff --git a/nexgb/xgbgen/field.go b/nexgb/xgbgen/field.go new file mode 100644 index 0000000..d6957b7 --- /dev/null +++ b/nexgb/xgbgen/field.go @@ -0,0 +1,398 @@ +package main + +import ( + "fmt" + "log" + "strings" +) + +// Field corresponds to any field described in an XML protocol description +// file. This includes struct fields, union fields, request fields, +// reply fields and so on. +// To make code generation easier, fields that have types are also stored. +// Note that not all fields support all methods defined in this interface. +// For instance, a padding field does not have a source name. +type Field interface { + // Initialize sets up the source name of this field. + Initialize(p *Protocol) + + // SrcName is the Go source name of this field. + SrcName() string + + // XmlName is the name of this field from the XML file. + XmlName() string + + // SrcType is the Go source type name of this field. + SrcType() string + + // Size returns an expression that computes the size (in bytes) + // of this field. + Size() Size + + // Define writes the Go code to declare this field (in a struct definition). + Define(c *Context) + + // Read writes the Go code to convert a byte slice to a Go value + // of this field. + // 'prefix' is the prefix of the name of the Go value. + Read(c *Context, prefix string) + + // Write writes the Go code to convert a Go value to a byte slice of + // this field. + // 'prefix' is the prefix of the name of the Go value. + Write(c *Context, prefix string) +} + +func (pad *PadField) Initialize(p *Protocol) {} + +// PadField represents any type of padding. It is omitted from +// definitions, but is used in Read/Write to increment the buffer index. +// It is also used in size calculation. +type PadField struct { + Bytes uint + Align uint16 +} + +func (p *PadField) SrcName() string { + panic("illegal to take source name of a pad field") +} + +func (p *PadField) XmlName() string { + panic("illegal to take XML name of a pad field") +} + +func (f *PadField) SrcType() string { + panic("it is illegal to call SrcType on a PadField field") +} + +func (p *PadField) Size() Size { + if p.Align > 0 { + return newFixedSize(uint(p.Align), false) + } else { + return newFixedSize(p.Bytes, true) + } +} + +type RequiredStartAlign struct { +} + +func (f *RequiredStartAlign) Initialize(p *Protocol) {} + +func (f *RequiredStartAlign) SrcName() string { + panic("illegal to take source name of a required_start_align field") +} + +func (f *RequiredStartAlign) XmlName() string { + panic("illegal to take XML name of a required_start_align field") +} + +func (f *RequiredStartAlign) SrcType() string { + panic("it is illegal to call SrcType on a required_start_align field") +} + +func (f *RequiredStartAlign) Size() Size { + return newFixedSize(0, true) +} + +func (f *RequiredStartAlign) Define(c *Context) {} + +func (f *RequiredStartAlign) Read(c *Context, prefix string) {} +func (f *RequiredStartAlign) Write(c *Context, prefix string) {} + +// SingleField represents most of the fields in an XML protocol description. +// It corresponds to any single value. +type SingleField struct { + srcName string + xmlName string + Type Type +} + +func (f *SingleField) Initialize(p *Protocol) { + f.srcName = SrcName(p, f.XmlName()) + f.Type = f.Type.(*Translation).RealType(p) +} + +func (f *SingleField) SrcName() string { + if f.srcName == "Bytes" { + return "Bytes_" + } + return f.srcName +} + +func (f *SingleField) XmlName() string { + return f.xmlName +} + +func (f *SingleField) SrcType() string { + return f.Type.SrcName() +} + +func (f *SingleField) Size() Size { + return f.Type.Size() +} + +// ListField represents a list of values. +type ListField struct { + srcName string + xmlName string + Type Type + LengthExpr Expression +} + +func (f *ListField) SrcName() string { + return f.srcName +} + +func (f *ListField) XmlName() string { + return f.xmlName +} + +func (f *ListField) SrcType() string { + if strings.ToLower(f.Type.XmlName()) == "char" { + return fmt.Sprintf("string") + } + return fmt.Sprintf("[]%s", f.Type.SrcName()) +} + +// Length computes the *number* of values in a list. +// If this ListField does not have any length expression, we throw our hands +// up and simply compute the 'len' of the field name of this list. +func (f *ListField) Length() Size { + if f.LengthExpr == nil { + return newExpressionSize(&Function{ + Name: "len", + Expr: &FieldRef{ + Name: f.SrcName(), + }, + }, true) + } + return newExpressionSize(f.LengthExpr, true) +} + +// Size computes the *size* of a list (in bytes). +// It it typically a simple matter of multiplying the length of the list by +// the size of the type of the list. +// But if it's a list of struct where the struct has a list field, we use a +// special function written in go_struct.go to compute the size (since the +// size in this case can only be computed recursively). +func (f *ListField) Size() Size { + elsz := f.Type.Size() + simpleLen := &Padding{ + Expr: newBinaryOp("*", f.Length().Expression, elsz.Expression), + } + + switch field := f.Type.(type) { + case *Struct: + if field.HasList() { + sizeFun := &Function{ + Name: fmt.Sprintf("%sListSize", f.Type.SrcName()), + Expr: &FieldRef{Name: f.SrcName()}, + } + return newExpressionSize(sizeFun, elsz.exact) + } else { + return newExpressionSize(simpleLen, elsz.exact) + } + case *Union: + return newExpressionSize(simpleLen, elsz.exact) + case *Base: + return newExpressionSize(simpleLen, elsz.exact) + case *Resource: + return newExpressionSize(simpleLen, elsz.exact) + case *TypeDef: + return newExpressionSize(simpleLen, elsz.exact) + default: + log.Panicf("Cannot compute list size with type '%T'.", f.Type) + } + panic("unreachable") +} + +func (f *ListField) Initialize(p *Protocol) { + f.srcName = SrcName(p, f.XmlName()) + f.Type = f.Type.(*Translation).RealType(p) + if f.LengthExpr != nil { + f.LengthExpr.Initialize(p) + } +} + +// LocalField is exactly the same as a regular SingleField, except it isn't +// sent over the wire. (i.e., it's probably used to compute an ExprField). +type LocalField struct { + *SingleField +} + +// ExprField is a field that is not parameterized, but is computed from values +// of other fields. +type ExprField struct { + srcName string + xmlName string + Type Type + Expr Expression +} + +func (f *ExprField) SrcName() string { + return f.srcName +} + +func (f *ExprField) XmlName() string { + return f.xmlName +} + +func (f *ExprField) SrcType() string { + return f.Type.SrcName() +} + +func (f *ExprField) Size() Size { + return f.Type.Size() +} + +func (f *ExprField) Initialize(p *Protocol) { + f.srcName = SrcName(p, f.XmlName()) + f.Type = f.Type.(*Translation).RealType(p) + f.Expr.Initialize(p) +} + +// ValueField represents two fields in one: a mask and a list of 4-byte +// integers. The mask specifies which kinds of values are in the list. +// (i.e., See ConfigureWindow, CreateWindow, ChangeWindowAttributes, etc.) +type ValueField struct { + Parent interface{} + MaskType Type + MaskName string + ListName string +} + +func (f *ValueField) SrcName() string { + panic("it is illegal to call SrcName on a ValueField field") +} + +func (f *ValueField) XmlName() string { + panic("it is illegal to call XmlName on a ValueField field") +} + +func (f *ValueField) SrcType() string { + return f.MaskType.SrcName() +} + +// Size computes the size in bytes of the combination of the mask and list +// in this value field. +// The expression to compute this looks complicated, but it's really just +// the number of bits set in the mask multiplied 4 (and padded of course). +func (f *ValueField) Size() Size { + maskSize := f.MaskType.Size() + listSize := newExpressionSize(&Function{ + Name: "xgb.Pad", + Expr: &BinaryOp{ + Op: "*", + Expr1: &Value{v: 4}, + Expr2: &PopCount{ + Expr: &Function{ + Name: "int", + Expr: &FieldRef{ + Name: f.MaskName, + }, + }, + }, + }, + }, true) + return maskSize.Add(listSize) +} + +func (f *ValueField) ListLength() Size { + return newExpressionSize(&PopCount{ + Expr: &Function{ + Name: "int", + Expr: &FieldRef{ + Name: f.MaskName, + }, + }, + }, true) +} + +func (f *ValueField) Initialize(p *Protocol) { + f.MaskType = f.MaskType.(*Translation).RealType(p) + f.MaskName = SrcName(p, f.MaskName) + f.ListName = SrcName(p, f.ListName) +} + +// SwitchField represents a 'switch' element in the XML protocol description +// file. +// Currently we translate this to a slice of uint32 and let the user sort +// through it. +type SwitchField struct { + xmlName string + Name string + MaskName string + Expr Expression + Bitcases []*Bitcase +} + +func (f *SwitchField) SrcName() string { + return f.Name +} + +func (f *SwitchField) XmlName() string { + return f.xmlName +} + +func (f *SwitchField) SrcType() string { + return "[]uint32" +} + +func (f *SwitchField) Size() Size { + // TODO: size expression used here is not correct unless every element of + // the switch is 32 bit long. This assumption holds for xproto but may not + // hold for other protocols (xkb?) + + listSize := newExpressionSize(&Function{ + Name: "xgb.Pad", + Expr: &BinaryOp{ + Op: "*", + Expr1: &Value{v: 4}, + Expr2: &PopCount{ + Expr: &Function{ + Name: "int", + Expr: &FieldRef{ + Name: f.MaskName, + }, + }, + }, + }, + }, true) + + return listSize +} + +func (f *SwitchField) ListLength() Size { + return newExpressionSize(&PopCount{ + Expr: &Function{ + Name: "int", + Expr: &FieldRef{ + Name: f.MaskName, + }, + }, + }, true) +} + +func (f *SwitchField) Initialize(p *Protocol) { + f.xmlName = f.Name + f.Name = SrcName(p, f.Name) + f.Expr.Initialize(p) + fieldref, ok := f.Expr.(*FieldRef) + if !ok { + panic("switch field's expression not a fieldref") + } + f.MaskName = SrcName(p, fieldref.Name) + for _, bitcase := range f.Bitcases { + bitcase.Expr.Initialize(p) + for _, field := range bitcase.Fields { + field.Initialize(p) + } + } +} + +// Bitcase represents a single bitcase inside a switch expression. +// It is not currently used. (i.e., it's XKB voodoo.) +type Bitcase struct { + Fields []Field + Expr Expression +} diff --git a/nexgb/xgbgen/go.go b/nexgb/xgbgen/go.go new file mode 100644 index 0000000..87b5028 --- /dev/null +++ b/nexgb/xgbgen/go.go @@ -0,0 +1,220 @@ +package main + +import ( + "fmt" +) + +// BaseTypeMap is a map from X base types to Go types. +// X base types should correspond to the smallest set of X types +// that can be used to rewrite ALL X types in terms of Go types. +// That is, if you remove any of the following types, at least one +// XML protocol description will produce an invalid Go program. +// The types on the left *never* show themselves in the source. +var BaseTypeMap = map[string]string{ + "CARD8": "byte", + "CARD16": "uint16", + "CARD32": "uint32", + "INT8": "int8", + "INT16": "int16", + "INT32": "int32", + "BYTE": "byte", + "BOOL": "bool", + "float": "float64", + "double": "float64", + "char": "byte", + "void": "byte", +} + +// BaseTypeSizes should have precisely the same keys as in BaseTypeMap, +// and the values should correspond to the size of the type in bytes. +var BaseTypeSizes = map[string]uint{ + "CARD8": 1, + "CARD16": 2, + "CARD32": 4, + "INT8": 1, + "INT16": 2, + "INT32": 4, + "BYTE": 1, + "BOOL": 1, + "float": 4, + "double": 8, + "char": 1, + "void": 1, + + // Id is a special type used to determine the size of all Xid types. + // "Id" is not actually written in the source. + "Id": 4, +} + +// TypeMap is a map from types in the XML to type names that is used +// in the functions that follow. Basically, every occurrence of the key +// type is replaced with the value type. +var TypeMap = map[string]string{ + "VISUALTYPE": "VisualInfo", + "DEPTH": "DepthInfo", + "SCREEN": "ScreenInfo", + "Setup": "SetupInfo", +} + +// NameMap is the same as TypeMap, but for names. +var NameMap = map[string]string{} + +// Reading, writing and defining... + +// Base types +func (b *Base) Define(c *Context) { + c.Putln("// Skipping definition for base type '%s'", + SrcName(c.protocol, b.XmlName())) + c.Putln("") +} + +// Enum types +func (enum *Enum) Define(c *Context) { + c.Putln("const (") + for _, item := range enum.Items { + c.Putln("%s%s = %d", enum.SrcName(), item.srcName, item.Expr.Eval()) + } + c.Putln(")") + c.Putln("") +} + +// Resource types +func (res *Resource) Define(c *Context) { + c.Putln("type %s uint32", res.SrcName()) + c.Putln("") + c.Putln("func New%sId(c *xgb.Conn) (%s, error) {", + res.SrcName(), res.SrcName()) + c.Putln("id, err := c.NewId()") + c.Putln("if err != nil {") + c.Putln("return 0, err") + c.Putln("}") + c.Putln("return %s(id), nil", res.SrcName()) + c.Putln("}") + c.Putln("") +} + +// TypeDef types +func (td *TypeDef) Define(c *Context) { + c.Putln("type %s %s", td.srcName, td.Old.SrcName()) + c.Putln("") +} + +// Field definitions, reads and writes. + +// Pad fields +func (f *PadField) Define(c *Context) { + if f.Align > 0 { + c.Putln("// alignment gap to multiple of %d", f.Align) + } else { + c.Putln("// padding: %d bytes", f.Bytes) + } +} + +func (f *PadField) Read(c *Context, prefix string) { + if f.Align > 0 { + c.Putln("b = (b + %d) & ^%d // alignment gap", f.Align-1, f.Align-1) + } else { + c.Putln("b += %s // padding", f.Size()) + } +} + +func (f *PadField) Write(c *Context, prefix string) { + if f.Align > 0 { + c.Putln("b = (b + %d) & ^%d // alignment gap", f.Align-1, f.Align-1) + } else { + c.Putln("b += %s // padding", f.Size()) + } +} + +// Local fields +func (f *LocalField) Define(c *Context) { + c.Putln("// local field: %s %s", f.SrcName(), f.Type.SrcName()) + panic("unreachable") +} + +func (f *LocalField) Read(c *Context, prefix string) { + c.Putln("// reading local field: %s (%s) :: %s", + f.SrcName(), f.Size(), f.Type.SrcName()) + panic("unreachable") +} + +func (f *LocalField) Write(c *Context, prefix string) { + c.Putln("// skip writing local field: %s (%s) :: %s", + f.SrcName(), f.Size(), f.Type.SrcName()) +} + +// Expr fields +func (f *ExprField) Define(c *Context) { + c.Putln("// expression field: %s %s (%s)", + f.SrcName(), f.Type.SrcName(), f.Expr) + panic("unreachable") +} + +func (f *ExprField) Read(c *Context, prefix string) { + c.Putln("// reading expression field: %s (%s) (%s) :: %s", + f.SrcName(), f.Size(), f.Expr, f.Type.SrcName()) + panic("unreachable") +} + +func (f *ExprField) Write(c *Context, prefix string) { + // Special case for bools, grrr. + if f.Type.SrcName() == "bool" { + c.Putln("buf[b] = byte(%s)", f.Expr.Reduce(prefix)) + c.Putln("b += 1") + } else { + WriteSimpleSingleField(c, f.Expr.Reduce(prefix), f.Type) + } +} + +// Value field +func (f *ValueField) Define(c *Context) { + c.Putln("%s %s", f.MaskName, f.SrcType()) + c.Putln("%s []uint32", f.ListName) +} + +func (f *ValueField) Read(c *Context, prefix string) { + ReadSimpleSingleField(c, + fmt.Sprintf("%s%s", prefix, f.MaskName), f.MaskType) + c.Putln("") + c.Putln("%s%s = make([]uint32, %s)", + prefix, f.ListName, f.ListLength().Reduce(prefix)) + c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix)) + c.Putln("%s%s[i] = xgb.Get32(buf[b:])", prefix, f.ListName) + c.Putln("b += 4") + c.Putln("}") + c.Putln("b = xgb.Pad(b)") +} + +func (f *ValueField) Write(c *Context, prefix string) { + WriteSimpleSingleField(c, + fmt.Sprintf("%s%s", prefix, f.MaskName), f.MaskType) + c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix)) + c.Putln("xgb.Put32(buf[b:], %s%s[i])", prefix, f.ListName) + c.Putln("b += 4") + c.Putln("}") + c.Putln("b = xgb.Pad(b)") +} + +// Switch field +func (f *SwitchField) Define(c *Context) { + c.Putln("%s []uint32", f.Name) +} + +func (f *SwitchField) Read(c *Context, prefix string) { + c.Putln("") + c.Putln("%s%s = make([]uint32, %s)", + prefix, f.Name, f.ListLength().Reduce(prefix)) + c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix)) + c.Putln("%s%s[i] = xgb.Get32(buf[b:])", prefix, f.Name) + c.Putln("b += 4") + c.Putln("}") + c.Putln("b = xgb.Pad(b)") +} + +func (f *SwitchField) Write(c *Context, prefix string) { + c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix)) + c.Putln("xgb.Put32(buf[b:], %s%s[i])", prefix, f.Name) + c.Putln("b += 4") + c.Putln("}") + c.Putln("b = xgb.Pad(b)") +} diff --git a/nexgb/xgbgen/go_error.go b/nexgb/xgbgen/go_error.go new file mode 100644 index 0000000..55fd28b --- /dev/null +++ b/nexgb/xgbgen/go_error.go @@ -0,0 +1,179 @@ +package main + +import ( + "fmt" +) + +// Error types +func (e *Error) Define(c *Context) { + c.Putln("// %s is the error number for a %s.", e.ErrConst(), e.ErrConst()) + c.Putln("const %s = %d", e.ErrConst(), e.Number) + c.Putln("") + c.Putln("type %s struct {", e.ErrType()) + c.Putln("Sequence uint16") + c.Putln("NiceName string") + for _, field := range e.Fields { + field.Define(c) + } + c.Putln("}") + c.Putln("") + + // Read defines a function that transforms a byte slice into this + // error struct. + e.Read(c) + + // Makes sure this error type implements the xgb.Error interface. + e.ImplementsError(c) + + // Let's the XGB event loop read this error. + c.Putln("func init() {") + if c.protocol.isExt() { + c.Putln("xgb.NewExtErrorFuncs[\"%s\"][%d] = %sNew", + c.protocol.ExtXName, e.Number, e.ErrType()) + } else { + c.Putln("xgb.NewErrorFuncs[%d] = %sNew", e.Number, e.ErrType()) + } + c.Putln("}") + c.Putln("") +} + +func (e *Error) Read(c *Context) { + c.Putln("// %sNew constructs a %s value that implements xgb.Error from "+ + "a byte slice.", e.ErrType(), e.ErrType()) + c.Putln("func %sNew(buf []byte) xgb.Error {", e.ErrType()) + c.Putln("v := %s{}", e.ErrType()) + c.Putln("v.NiceName = \"%s\"", e.SrcName()) + c.Putln("") + c.Putln("b := 1 // skip error determinant") + c.Putln("b += 1 // don't read error number") + c.Putln("") + c.Putln("v.Sequence = xgb.Get16(buf[b:])") + c.Putln("b += 2") + c.Putln("") + for _, field := range e.Fields { + field.Read(c, "v.") + c.Putln("") + } + c.Putln("return v") + c.Putln("}") + c.Putln("") +} + +// ImplementsError writes functions to implement the XGB Error interface. +func (e *Error) ImplementsError(c *Context) { + c.Putln("// SequenceId returns the sequence id attached to the %s error.", + e.ErrConst()) + c.Putln("// This is mostly used internally.") + c.Putln("func (err %s) SequenceId() uint16 {", e.ErrType()) + c.Putln("return err.Sequence") + c.Putln("}") + c.Putln("") + c.Putln("// BadId returns the 'BadValue' number if one exists for the "+ + "%s error. If no bad value exists, 0 is returned.", e.ErrConst()) + c.Putln("func (err %s) BadId() uint32 {", e.ErrType()) + if !c.protocol.isExt() { + c.Putln("return err.BadValue") + } else { + c.Putln("return 0") + } + c.Putln("}") + c.Putln("// Error returns a rudimentary string representation of the %s "+ + "error.", e.ErrConst()) + c.Putln("") + c.Putln("func (err %s) Error() string {", e.ErrType()) + ErrorFieldString(c, e.Fields, e.ErrConst()) + c.Putln("}") + c.Putln("") +} + +// ErrorCopy types +func (e *ErrorCopy) Define(c *Context) { + c.Putln("// %s is the error number for a %s.", e.ErrConst(), e.ErrConst()) + c.Putln("const %s = %d", e.ErrConst(), e.Number) + c.Putln("") + c.Putln("type %s %s", e.ErrType(), e.Old.(*Error).ErrType()) + c.Putln("") + + // Read defines a function that transforms a byte slice into this + // error struct. + e.Read(c) + + // Makes sure this error type implements the xgb.Error interface. + e.ImplementsError(c) + + // Let's the XGB know how to read this error. + c.Putln("func init() {") + if c.protocol.isExt() { + c.Putln("xgb.NewExtErrorFuncs[\"%s\"][%d] = %sNew", + c.protocol.ExtXName, e.Number, e.ErrType()) + } else { + c.Putln("xgb.NewErrorFuncs[%d] = %sNew", e.Number, e.ErrType()) + } + c.Putln("}") + c.Putln("") +} + +func (e *ErrorCopy) Read(c *Context) { + c.Putln("// %sNew constructs a %s value that implements xgb.Error from "+ + "a byte slice.", e.ErrType(), e.ErrType()) + c.Putln("func %sNew(buf []byte) xgb.Error {", e.ErrType()) + c.Putln("v := %s(%sNew(buf).(%s))", + e.ErrType(), e.Old.(*Error).ErrType(), e.Old.(*Error).ErrType()) + c.Putln("v.NiceName = \"%s\"", e.SrcName()) + c.Putln("return v") + c.Putln("}") + c.Putln("") +} + +// ImplementsError writes functions to implement the XGB Error interface. +func (e *ErrorCopy) ImplementsError(c *Context) { + c.Putln("// SequenceId returns the sequence id attached to the %s error.", + e.ErrConst()) + c.Putln("// This is mostly used internally.") + c.Putln("func (err %s) SequenceId() uint16 {", e.ErrType()) + c.Putln("return err.Sequence") + c.Putln("}") + c.Putln("") + c.Putln("// BadId returns the 'BadValue' number if one exists for the "+ + "%s error. If no bad value exists, 0 is returned.", e.ErrConst()) + c.Putln("func (err %s) BadId() uint32 {", e.ErrType()) + if !c.protocol.isExt() { + c.Putln("return err.BadValue") + } else { + c.Putln("return 0") + } + c.Putln("}") + c.Putln("") + c.Putln("// Error returns a rudimentary string representation of the %s "+ + "error.", e.ErrConst()) + c.Putln("func (err %s) Error() string {", e.ErrType()) + ErrorFieldString(c, e.Old.(*Error).Fields, e.ErrConst()) + c.Putln("}") + c.Putln("") +} + +// ErrorFieldString works for both Error and ErrorCopy. It assembles all of the +// fields in an error and formats them into a single string. +func ErrorFieldString(c *Context, fields []Field, errName string) { + c.Putln("fieldVals := make([]string, 0, %d)", len(fields)) + c.Putln("fieldVals = append(fieldVals, \"NiceName: \" + err.NiceName)") + c.Putln("fieldVals = append(fieldVals, "+ + "xgb.Sprintf(\"Sequence: %s\", err.Sequence))", "%d") + for _, field := range fields { + switch field.(type) { + case *PadField: + continue + default: + if field.SrcType() == "string" { + c.Putln("fieldVals = append(fieldVals, \"%s: \" + err.%s)", + field.SrcName(), field.SrcName()) + } else { + format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", err.%s)", + field.SrcName(), "%d", field.SrcName()) + c.Putln("fieldVals = append(fieldVals, %s)", format) + } + } + } + c.Putln("return \"%s {\" + xgb.StringsJoin(fieldVals, \", \") + \"}\"", + errName) +} diff --git a/nexgb/xgbgen/go_event.go b/nexgb/xgbgen/go_event.go new file mode 100644 index 0000000..9b5e748 --- /dev/null +++ b/nexgb/xgbgen/go_event.go @@ -0,0 +1,209 @@ +package main + +import ( + "fmt" +) + +// Event types +func (e *Event) Define(c *Context) { + c.Putln("// %s is the event number for a %s.", e.SrcName(), e.EvType()) + c.Putln("const %s = %d", e.SrcName(), e.Number) + c.Putln("") + c.Putln("type %s struct {", e.EvType()) + if !e.NoSequence { + c.Putln("Sequence uint16") + } + for _, field := range e.Fields { + field.Define(c) + } + c.Putln("}") + c.Putln("") + + // Read defines a function that transforms a byte slice into this + // event struct. + e.Read(c) + + // Write defines a function that transforms this event struct into + // a byte slice. + e.Write(c) + + // Makes sure that this event type is an Event interface. + c.Putln("// SequenceId returns the sequence id attached to the %s event.", + e.SrcName()) + c.Putln("// Events without a sequence number (KeymapNotify) return 0.") + c.Putln("// This is mostly used internally.") + c.Putln("func (v %s) SequenceId() uint16 {", e.EvType()) + if e.NoSequence { + c.Putln("return uint16(0)") + } else { + c.Putln("return v.Sequence") + } + c.Putln("}") + c.Putln("") + c.Putln("// String is a rudimentary string representation of %s.", + e.EvType()) + c.Putln("func (v %s) String() string {", e.EvType()) + EventFieldString(c, e.Fields, e.SrcName()) + c.Putln("}") + c.Putln("") + + // Let's the XGB event loop read this event. + c.Putln("func init() {") + if c.protocol.isExt() { + c.Putln("xgb.NewExtEventFuncs[\"%s\"][%d] = %sNew", + c.protocol.ExtXName, e.Number, e.EvType()) + } else { + c.Putln("xgb.NewEventFuncs[%d] = %sNew", e.Number, e.EvType()) + } + c.Putln("}") + c.Putln("") +} + +func (e *Event) Read(c *Context) { + c.Putln("// %sNew constructs a %s value that implements xgb.Event from "+ + "a byte slice.", e.EvType(), e.EvType()) + c.Putln("func %sNew(buf []byte) xgb.Event {", e.EvType()) + c.Putln("v := %s{}", e.EvType()) + c.Putln("b := 1 // don't read event number") + c.Putln("") + for i, field := range e.Fields { + if i == 1 && !e.NoSequence { + c.Putln("v.Sequence = xgb.Get16(buf[b:])") + c.Putln("b += 2") + c.Putln("") + } + field.Read(c, "v.") + c.Putln("") + } + c.Putln("return v") + c.Putln("}") + c.Putln("") +} + +func (e *Event) Write(c *Context) { + c.Putln("// Bytes writes a %s value to a byte slice.", e.EvType()) + c.Putln("func (v %s) Bytes() []byte {", e.EvType()) + c.Putln("buf := make([]byte, %s)", e.Size()) + c.Putln("b := 0") + c.Putln("") + c.Putln("// write event number") + c.Putln("buf[b] = %d", e.Number) + c.Putln("b += 1") + c.Putln("") + for i, field := range e.Fields { + if i == 1 && !e.NoSequence { + c.Putln("b += 2 // skip sequence number") + c.Putln("") + } + field.Write(c, "v.") + c.Putln("") + } + c.Putln("return buf") + c.Putln("}") + c.Putln("") +} + +// EventCopy types +func (e *EventCopy) Define(c *Context) { + c.Putln("// %s is the event number for a %s.", e.SrcName(), e.EvType()) + c.Putln("const %s = %d", e.SrcName(), e.Number) + c.Putln("") + c.Putln("type %s %s", e.EvType(), e.Old.(*Event).EvType()) + c.Putln("") + + // Read defines a function that transforms a byte slice into this + // event struct. + e.Read(c) + + // Write defines a function that transoforms this event struct into + // a byte slice. + e.Write(c) + + // Makes sure that this event type is an Event interface. + c.Putln("// SequenceId returns the sequence id attached to the %s event.", + e.SrcName()) + c.Putln("// Events without a sequence number (KeymapNotify) return 0.") + c.Putln("// This is mostly used internally.") + c.Putln("func (v %s) SequenceId() uint16 {", e.EvType()) + if e.Old.(*Event).NoSequence { + c.Putln("return uint16(0)") + } else { + c.Putln("return v.Sequence") + } + c.Putln("}") + c.Putln("") + c.Putln("func (v %s) String() string {", e.EvType()) + EventFieldString(c, e.Old.(*Event).Fields, e.SrcName()) + c.Putln("}") + c.Putln("") + + // Let's the XGB event loop read this event. + c.Putln("func init() {") + if c.protocol.isExt() { + c.Putln("xgb.NewExtEventFuncs[\"%s\"][%d] = %sNew", + c.protocol.ExtXName, e.Number, e.EvType()) + } else { + c.Putln("xgb.NewEventFuncs[%d] = %sNew", e.Number, e.EvType()) + } + c.Putln("}") + c.Putln("") +} + +func (e *EventCopy) Read(c *Context) { + c.Putln("// %sNew constructs a %s value that implements xgb.Event from "+ + "a byte slice.", e.EvType(), e.EvType()) + c.Putln("func %sNew(buf []byte) xgb.Event {", e.EvType()) + c.Putln("return %s(%sNew(buf).(%s))", + e.EvType(), e.Old.(*Event).EvType(), e.Old.(*Event).EvType()) + c.Putln("}") + c.Putln("") +} + +func (e *EventCopy) Write(c *Context) { + c.Putln("// Bytes writes a %s value to a byte slice.", e.EvType()) + c.Putln("func (v %s) Bytes() []byte {", e.EvType()) + c.Putln("return %s(v).Bytes()", e.Old.(*Event).EvType()) + c.Putln("}") + c.Putln("") +} + +// EventFieldString works for both Event and EventCopy. It assembles all of the +// fields in an event and formats them into a single string. +func EventFieldString(c *Context, fields []Field, evName string) { + c.Putln("fieldVals := make([]string, 0, %d)", len(fields)) + if evName != "KeymapNotify" { + c.Putln("fieldVals = append(fieldVals, "+ + "xgb.Sprintf(\"Sequence: %s\", v.Sequence))", "%d") + } + for _, field := range fields { + switch f := field.(type) { + case *PadField: + continue + case *SingleField: + switch f.Type.(type) { + case *Base: + case *Resource: + case *TypeDef: + default: + continue + } + + switch field.SrcType() { + case "string": + format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)", + field.SrcName(), "%s", field.SrcName()) + c.Putln("fieldVals = append(fieldVals, %s)", format) + case "bool": + format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)", + field.SrcName(), "%t", field.SrcName()) + c.Putln("fieldVals = append(fieldVals, %s)", format) + default: + format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)", + field.SrcName(), "%d", field.SrcName()) + c.Putln("fieldVals = append(fieldVals, %s)", format) + } + } + } + c.Putln("return \"%s {\" + xgb.StringsJoin(fieldVals, \", \") + \"}\"", + evName) +} diff --git a/nexgb/xgbgen/go_list.go b/nexgb/xgbgen/go_list.go new file mode 100644 index 0000000..1e85d9f --- /dev/null +++ b/nexgb/xgbgen/go_list.go @@ -0,0 +1,107 @@ +package main + +import ( + "fmt" + "log" + "strings" +) + +// List fields +func (f *ListField) Define(c *Context) { + c.Putln("%s %s // size: %s", + f.SrcName(), f.SrcType(), f.Size()) +} + +func (f *ListField) Read(c *Context, prefix string) { + switch t := f.Type.(type) { + case *Resource: + length := f.LengthExpr.Reduce(prefix) + c.Putln("%s%s = make([]%s, %s)", + prefix, f.SrcName(), t.SrcName(), length) + c.Putln("for i := 0; i < int(%s); i++ {", length) + ReadSimpleSingleField(c, fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) + c.Putln("}") + case *Base: + length := f.LengthExpr.Reduce(prefix) + if strings.ToLower(t.XmlName()) == "char" { + c.Putln("{") + c.Putln("byteString := make([]%s, %s)", t.SrcName(), length) + c.Putln("copy(byteString[:%s], buf[b:])", length) + c.Putln("%s%s = string(byteString)", prefix, f.SrcName()) + // This is apparently a special case. The "Str" type itself + // doesn't specify any padding. I suppose it's up to the + // request/reply spec that uses it to get the padding right? + c.Putln("b += int(%s)", length) + c.Putln("}") + } else if t.SrcName() == "byte" { + c.Putln("%s%s = make([]%s, %s)", + prefix, f.SrcName(), t.SrcName(), length) + c.Putln("copy(%s%s[:%s], buf[b:])", prefix, f.SrcName(), length) + c.Putln("b += int(%s)", length) + } else { + c.Putln("%s%s = make([]%s, %s)", + prefix, f.SrcName(), t.SrcName(), length) + c.Putln("for i := 0; i < int(%s); i++ {", length) + ReadSimpleSingleField(c, + fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) + c.Putln("}") + } + case *TypeDef: + length := f.LengthExpr.Reduce(prefix) + c.Putln("%s%s = make([]%s, %s)", + prefix, f.SrcName(), t.SrcName(), length) + c.Putln("for i := 0; i < int(%s); i++ {", length) + ReadSimpleSingleField(c, fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) + c.Putln("}") + case *Union: + c.Putln("%s%s = make([]%s, %s)", + prefix, f.SrcName(), t.SrcName(), f.LengthExpr.Reduce(prefix)) + c.Putln("b += %sReadList(buf[b:], %s%s)", + t.SrcName(), prefix, f.SrcName()) + case *Struct: + c.Putln("%s%s = make([]%s, %s)", + prefix, f.SrcName(), t.SrcName(), f.LengthExpr.Reduce(prefix)) + c.Putln("b += %sReadList(buf[b:], %s%s)", + t.SrcName(), prefix, f.SrcName()) + default: + log.Panicf("Cannot read list field '%s' with %T type.", + f.XmlName(), f.Type) + } +} + +func (f *ListField) Write(c *Context, prefix string) { + switch t := f.Type.(type) { + case *Resource: + length := f.Length().Reduce(prefix) + c.Putln("for i := 0; i < int(%s); i++ {", length) + WriteSimpleSingleField(c, + fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) + c.Putln("}") + case *Base: + length := f.Length().Reduce(prefix) + if t.SrcName() == "byte" { + c.Putln("copy(buf[b:], %s%s[:%s])", prefix, f.SrcName(), length) + c.Putln("b += int(%s)", length) + } else { + c.Putln("for i := 0; i < int(%s); i++ {", length) + WriteSimpleSingleField(c, + fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) + c.Putln("}") + } + case *TypeDef: + length := f.Length().Reduce(prefix) + c.Putln("for i := 0; i < int(%s); i++ {", length) + WriteSimpleSingleField(c, + fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) + c.Putln("}") + case *Union: + c.Putln("b += %sListBytes(buf[b:], %s%s)", + t.SrcName(), prefix, f.SrcName()) + case *Struct: + c.Putln("b += %sListBytes(buf[b:], %s%s)", + t.SrcName(), prefix, f.SrcName()) + default: + log.Panicf("Cannot write list field '%s' with %T type.", + f.XmlName(), f.Type) + } +} diff --git a/nexgb/xgbgen/go_request_reply.go b/nexgb/xgbgen/go_request_reply.go new file mode 100644 index 0000000..9cadc33 --- /dev/null +++ b/nexgb/xgbgen/go_request_reply.go @@ -0,0 +1,242 @@ +package main + +import ( + "fmt" + "strings" +) + +func (r *Request) Define(c *Context) { + c.Putln("// %s is a cookie used only for %s requests.", + r.CookieName(), r.SrcName()) + c.Putln("type %s struct {", r.CookieName()) + c.Putln("*xgb.Cookie") + c.Putln("}") + c.Putln("") + if r.Reply != nil { + c.Putln("// %s sends a checked request.", r.SrcName()) + c.Putln("// If an error occurs, it will be returned with the reply "+ + "by calling %s.Reply()", r.CookieName()) + c.Putln("func %s(c *xgb.Conn, %s) %s {", + r.SrcName(), r.ParamNameTypes(), r.CookieName()) + r.CheckExt(c) + c.Putln("cookie := c.NewCookie(true, true)") + c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames()) + c.Putln("return %s{cookie}", r.CookieName()) + c.Putln("}") + c.Putln("") + + c.Putln("// %sUnchecked sends an unchecked request.", r.SrcName()) + c.Putln("// If an error occurs, it can only be retrieved using " + + "xgb.WaitForEvent or xgb.PollForEvent.") + c.Putln("func %sUnchecked(c *xgb.Conn, %s) %s {", + r.SrcName(), r.ParamNameTypes(), r.CookieName()) + r.CheckExt(c) + c.Putln("cookie := c.NewCookie(false, true)") + c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames()) + c.Putln("return %s{cookie}", r.CookieName()) + c.Putln("}") + c.Putln("") + + r.ReadReply(c) + } else { + c.Putln("// %s sends an unchecked request.", r.SrcName()) + c.Putln("// If an error occurs, it can only be retrieved using " + + "xgb.WaitForEvent or xgb.PollForEvent.") + c.Putln("func %s(c *xgb.Conn, %s) %s {", + r.SrcName(), r.ParamNameTypes(), r.CookieName()) + r.CheckExt(c) + c.Putln("cookie := c.NewCookie(false, false)") + c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames()) + c.Putln("return %s{cookie}", r.CookieName()) + c.Putln("}") + c.Putln("") + + c.Putln("// %sChecked sends a checked request.", r.SrcName()) + c.Putln("// If an error occurs, it can be retrieved using "+ + "%s.Check()", r.CookieName()) + c.Putln("func %sChecked(c *xgb.Conn, %s) %s {", + r.SrcName(), r.ParamNameTypes(), r.CookieName()) + r.CheckExt(c) + c.Putln("cookie := c.NewCookie(true, false)") + c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames()) + c.Putln("return %s{cookie}", r.CookieName()) + c.Putln("}") + c.Putln("") + + c.Putln("// Check returns an error if one occurred for checked " + + "requests that are not expecting a reply.") + c.Putln("// This cannot be called for requests expecting a reply, " + + "nor for unchecked requests.") + c.Putln("func (cook %s) Check() error {", r.CookieName()) + c.Putln("return cook.Cookie.Check()") + c.Putln("}") + c.Putln("") + } + r.WriteRequest(c) +} + +func (r *Request) CheckExt(c *Context) { + if !c.protocol.isExt() { + return + } + c.Putln("c.ExtLock.RLock()") + c.Putln("defer c.ExtLock.RUnlock()") + c.Putln("if _, ok := c.Extensions[\"%s\"]; !ok {", c.protocol.ExtXName) + c.Putln("panic(\"Cannot issue request '%s' using the uninitialized "+ + "extension '%s'. %s.Init(connObj) must be called first.\")", + r.SrcName(), c.protocol.ExtXName, c.protocol.PkgName()) + c.Putln("}") +} + +func (r *Request) ReadReply(c *Context) { + c.Putln("// %s represents the data returned from a %s request.", + r.ReplyTypeName(), r.SrcName()) + c.Putln("type %s struct {", r.ReplyTypeName()) + c.Putln("Sequence uint16 // sequence number of the request for this reply") + c.Putln("Length uint32 // number of bytes in this reply") + for _, field := range r.Reply.Fields { + field.Define(c) + } + c.Putln("}") + c.Putln("") + + c.Putln("// Reply blocks and returns the reply data for a %s request.", + r.SrcName()) + c.Putln("func (cook %s) Reply() (*%s, error) {", + r.CookieName(), r.ReplyTypeName()) + c.Putln("buf, err := cook.Cookie.Reply()") + c.Putln("if err != nil {") + c.Putln("return nil, err") + c.Putln("}") + c.Putln("if buf == nil {") + c.Putln("return nil, nil") + c.Putln("}") + c.Putln("return %s(buf), nil", r.ReplyName()) + c.Putln("}") + c.Putln("") + + c.Putln("// %s reads a byte slice into a %s value.", + r.ReplyName(), r.ReplyTypeName()) + c.Putln("func %s(buf []byte) *%s {", + r.ReplyName(), r.ReplyTypeName()) + c.Putln("v := new(%s)", r.ReplyTypeName()) + c.Putln("b := 1 // skip reply determinant") + c.Putln("") + for i, field := range r.Reply.Fields { + field.Read(c, "v.") + c.Putln("") + if i == 0 { + c.Putln("v.Sequence = xgb.Get16(buf[b:])") + c.Putln("b += 2") + c.Putln("") + c.Putln("v.Length = xgb.Get32(buf[b:]) // 4-byte units") + c.Putln("b += 4") + c.Putln("") + } + } + c.Putln("return v") + c.Putln("}") + c.Putln("") +} + +func (r *Request) WriteRequest(c *Context) { + sz := r.Size(c) + writeSize1 := func() { + if sz.exact { + c.Putln("xgb.Put16(buf[b:], uint16(size / 4)) " + + "// write request size in 4-byte units") + } else { + c.Putln("blen := b") + } + c.Putln("b += 2") + c.Putln("") + } + writeSize2 := func() { + if sz.exact { + c.Putln("return buf") + return + } + c.Putln("b = xgb.Pad(b)") + c.Putln("xgb.Put16(buf[blen:], uint16(b / 4)) " + + "// write request size in 4-byte units") + c.Putln("return buf[:b]") + } + c.Putln("// Write request to wire for %s", r.SrcName()) + c.Putln("// %s writes a %s request to a byte slice.", + r.ReqName(), r.SrcName()) + c.Putln("func %s(c *xgb.Conn, %s) []byte {", + r.ReqName(), r.ParamNameTypes()) + c.Putln("size := %s", sz) + c.Putln("b := 0") + c.Putln("buf := make([]byte, size)") + c.Putln("") + if c.protocol.isExt() { + c.Putln("c.ExtLock.RLock()") + c.Putln("buf[b] = c.Extensions[\"%s\"]", c.protocol.ExtXName) + c.Putln("c.ExtLock.RUnlock()") + c.Putln("b += 1") + c.Putln("") + } + c.Putln("buf[b] = %d // request opcode", r.Opcode) + c.Putln("b += 1") + c.Putln("") + if len(r.Fields) == 0 { + if !c.protocol.isExt() { + c.Putln("b += 1 // padding") + } + writeSize1() + } else if c.protocol.isExt() { + writeSize1() + } + for i, field := range r.Fields { + field.Write(c, "") + c.Putln("") + if i == 0 && !c.protocol.isExt() { + writeSize1() + } + } + writeSize2() + c.Putln("}") + c.Putln("") +} + +func (r *Request) ParamNames() string { + names := make([]string, 0, len(r.Fields)) + for _, field := range r.Fields { + switch f := field.(type) { + case *ValueField: + names = append(names, f.MaskName) + names = append(names, f.ListName) + case *PadField: + continue + case *ExprField: + continue + default: + names = append(names, fmt.Sprintf("%s", field.SrcName())) + } + } + return strings.Join(names, ", ") +} + +func (r *Request) ParamNameTypes() string { + nameTypes := make([]string, 0, len(r.Fields)) + for _, field := range r.Fields { + switch f := field.(type) { + case *ValueField: + nameTypes = append(nameTypes, + fmt.Sprintf("%s %s", f.MaskName, f.MaskType.SrcName())) + nameTypes = append(nameTypes, + fmt.Sprintf("%s []uint32", f.ListName)) + case *PadField: + continue + case *ExprField: + continue + case *RequiredStartAlign: + continue + default: + nameTypes = append(nameTypes, + fmt.Sprintf("%s %s", field.SrcName(), field.SrcType())) + } + } + return strings.Join(nameTypes, ", ") +} diff --git a/nexgb/xgbgen/go_single_field.go b/nexgb/xgbgen/go_single_field.go new file mode 100644 index 0000000..6c7218e --- /dev/null +++ b/nexgb/xgbgen/go_single_field.go @@ -0,0 +1,166 @@ +package main + +import ( + "fmt" + "log" +) + +func (f *SingleField) Define(c *Context) { + c.Putln("%s %s", f.SrcName(), f.Type.SrcName()) +} + +func ReadSimpleSingleField(c *Context, name string, typ Type) { + switch t := typ.(type) { + case *Resource: + c.Putln("%s = %s(xgb.Get32(buf[b:]))", name, t.SrcName()) + case *TypeDef: + switch t.Size().Eval() { + case 1: + c.Putln("%s = %s(buf[b])", name, t.SrcName()) + case 2: + c.Putln("%s = %s(xgb.Get16(buf[b:]))", name, t.SrcName()) + case 4: + c.Putln("%s = %s(xgb.Get32(buf[b:]))", name, t.SrcName()) + case 8: + c.Putln("%s = %s(xgb.Get64(buf[b:]))", name, t.SrcName()) + } + case *Base: + // If this is a bool, stop short and do something special. + if t.SrcName() == "bool" { + c.Putln("if buf[b] == 1 {") + c.Putln("%s = true", name) + c.Putln("} else {") + c.Putln("%s = false", name) + c.Putln("}") + break + } + + var val string + switch t.Size().Eval() { + case 1: + val = fmt.Sprintf("buf[b]") + case 2: + val = fmt.Sprintf("xgb.Get16(buf[b:])") + case 4: + val = fmt.Sprintf("xgb.Get32(buf[b:])") + case 8: + val = fmt.Sprintf("xgb.Get64(buf[b:])") + } + + // We need to convert base types if they aren't uintXX or byte + ty := t.SrcName() + if ty != "byte" && ty != "uint16" && ty != "uint32" && ty != "uint64" { + val = fmt.Sprintf("%s(%s)", ty, val) + } + c.Putln("%s = %s", name, val) + default: + log.Panicf("Cannot read field '%s' as a simple field with %T type.", + name, typ) + } + + c.Putln("b += %s", typ.Size()) +} + +func (f *SingleField) Read(c *Context, prefix string) { + switch t := f.Type.(type) { + case *Resource: + ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) + case *TypeDef: + ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) + case *Base: + ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) + case *Struct: + c.Putln("%s%s = %s{}", prefix, f.SrcName(), t.SrcName()) + c.Putln("b += %sRead(buf[b:], &%s%s)", t.SrcName(), prefix, f.SrcName()) + case *Union: + c.Putln("%s%s = %s{}", prefix, f.SrcName(), t.SrcName()) + c.Putln("b += %sRead(buf[b:], &%s%s)", t.SrcName(), prefix, f.SrcName()) + default: + log.Panicf("Cannot read field '%s' with %T type.", f.XmlName(), f.Type) + } +} + +func WriteSimpleSingleField(c *Context, name string, typ Type) { + switch t := typ.(type) { + case *Resource: + c.Putln("xgb.Put32(buf[b:], uint32(%s))", name) + case *TypeDef: + switch t.Size().Eval() { + case 1: + c.Putln("buf[b] = byte(%s)", name) + case 2: + c.Putln("xgb.Put16(buf[b:], uint16(%s))", name) + case 4: + c.Putln("xgb.Put32(buf[b:], uint32(%s))", name) + case 8: + c.Putln("xgb.Put64(buf[b:], uint64(%s))", name) + } + case *Base: + // If this is a bool, stop short and do something special. + if t.SrcName() == "bool" { + c.Putln("if %s {", name) + c.Putln("buf[b] = 1") + c.Putln("} else {") + c.Putln("buf[b] = 0") + c.Putln("}") + break + } + + switch t.Size().Eval() { + case 1: + if t.SrcName() != "byte" { + c.Putln("buf[b] = byte(%s)", name) + } else { + c.Putln("buf[b] = %s", name) + } + case 2: + if t.SrcName() != "uint16" { + c.Putln("xgb.Put16(buf[b:], uint16(%s))", name) + } else { + c.Putln("xgb.Put16(buf[b:], %s)", name) + } + case 4: + if t.SrcName() != "uint32" { + c.Putln("xgb.Put32(buf[b:], uint32(%s))", name) + } else { + c.Putln("xgb.Put32(buf[b:], %s)", name) + } + case 8: + if t.SrcName() != "uint64" { + c.Putln("xgb.Put64(buf[b:], uint64(%s))", name) + } else { + c.Putln("xgb.Put64(buf[b:], %s)", name) + } + } + default: + log.Fatalf("Cannot read field '%s' as a simple field with %T type.", + name, typ) + } + + c.Putln("b += %s", typ.Size()) +} + +func (f *SingleField) Write(c *Context, prefix string) { + switch t := f.Type.(type) { + case *Resource: + WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) + case *TypeDef: + WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) + case *Base: + WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) + case *Union: + c.Putln("{") + c.Putln("unionBytes := %s%s.Bytes()", prefix, f.SrcName()) + c.Putln("copy(buf[b:], unionBytes)") + c.Putln("b += len(unionBytes)") + c.Putln("}") + case *Struct: + c.Putln("{") + c.Putln("structBytes := %s%s.Bytes()", prefix, f.SrcName()) + c.Putln("copy(buf[b:], structBytes)") + c.Putln("b += len(structBytes)") + c.Putln("}") + default: + log.Fatalf("Cannot read field '%s' with %T type.", f.XmlName(), f.Type) + } +} diff --git a/nexgb/xgbgen/go_struct.go b/nexgb/xgbgen/go_struct.go new file mode 100644 index 0000000..ee74d90 --- /dev/null +++ b/nexgb/xgbgen/go_struct.go @@ -0,0 +1,118 @@ +package main + +func (s *Struct) Define(c *Context) { + c.Putln("type %s struct {", s.SrcName()) + for _, field := range s.Fields { + field.Define(c) + } + c.Putln("}") + c.Putln("") + + // Write function that reads bytes and produces this struct. + s.Read(c) + + // Write function that reads bytes and produces a list of this struct. + s.ReadList(c) + + // Write function that writes bytes given this struct. + s.Write(c) + + // Write function that writes a list of this struct. + s.WriteList(c) + + // Write function that computes the size of a list of these structs, + // IF there is a list field in this struct. + if s.HasList() { + s.WriteListSize(c) + } +} + +// Read for a struct creates a function 'ReadStructName' that takes a source +// byte slice (i.e., the buffer) and a destination struct, and returns +// the number of bytes read off the buffer. +// 'ReadStructName' should only be used to read raw reply data from the wire. +func (s *Struct) Read(c *Context) { + c.Putln("// %sRead reads a byte slice into a %s value.", + s.SrcName(), s.SrcName()) + c.Putln("func %sRead(buf []byte, v *%s) int {", s.SrcName(), s.SrcName()) + + c.Putln("b := 0") + c.Putln("") + for _, field := range s.Fields { + field.Read(c, "v.") + c.Putln("") + } + c.Putln("return b") + + c.Putln("}") + c.Putln("") +} + +// ReadList for a struct creates a function 'ReadStructNameList' that takes +// a source (i.e., the buffer) byte slice, and a destination slice and returns +// the number of bytes read from the byte slice. +func (s *Struct) ReadList(c *Context) { + c.Putln("// %sReadList reads a byte slice into a list of %s values.", + s.SrcName(), s.SrcName()) + c.Putln("func %sReadList(buf []byte, dest []%s) int {", + s.SrcName(), s.SrcName()) + c.Putln("b := 0") + c.Putln("for i := 0; i < len(dest); i++ {") + c.Putln("dest[i] = %s{}", s.SrcName()) + c.Putln("b += %sRead(buf[b:], &dest[i])", s.SrcName()) + c.Putln("}") + + c.Putln("return xgb.Pad(b)") + + c.Putln("}") + c.Putln("") +} + +func (s *Struct) Write(c *Context) { + c.Putln("// Bytes writes a %s value to a byte slice.", s.SrcName()) + c.Putln("func (v %s) Bytes() []byte {", s.SrcName()) + c.Putln("buf := make([]byte, %s)", s.Size().Reduce("v.")) + c.Putln("b := 0") + c.Putln("") + for _, field := range s.Fields { + field.Write(c, "v.") + c.Putln("") + } + c.Putln("return buf[:b]") + c.Putln("}") + c.Putln("") +} + +func (s *Struct) WriteList(c *Context) { + c.Putln("// %sListBytes writes a list of %s values to a byte slice.", + s.SrcName(), s.SrcName()) + c.Putln("func %sListBytes(buf []byte, list []%s) int {", + s.SrcName(), s.SrcName()) + c.Putln("b := 0") + c.Putln("var structBytes []byte") + c.Putln("for _, item := range list {") + c.Putln("structBytes = item.Bytes()") + c.Putln("copy(buf[b:], structBytes)") + c.Putln("b += len(structBytes)") + c.Putln("}") + c.Putln("return xgb.Pad(b)") + c.Putln("}") + c.Putln("") +} + +func (s *Struct) WriteListSize(c *Context) { + c.Putln("// %sListSize computes the size (bytes) of a list of %s values.", + s.SrcName(), s.SrcName()) + c.Putln("func %sListSize(list []%s) int {", s.SrcName(), s.SrcName()) + c.Putln("size := 0") + if s.Size().Expression.Concrete() { + c.Putln("for _ = range list {") + } else { + c.Putln("for _, item := range list {") + } + c.Putln("size += %s", s.Size().Reduce("item.")) + c.Putln("}") + c.Putln("return size") + c.Putln("}") + c.Putln("") +} diff --git a/nexgb/xgbgen/go_union.go b/nexgb/xgbgen/go_union.go new file mode 100644 index 0000000..74816d3 --- /dev/null +++ b/nexgb/xgbgen/go_union.go @@ -0,0 +1,147 @@ +package main + +// Union types +func (u *Union) Define(c *Context) { + c.Putln("// %s is a represention of the %s union type.", + u.SrcName(), u.SrcName()) + c.Putln("// Note that to *create* a Union, you should *never* create") + c.Putln("// this struct directly (unless you know what you're doing).") + c.Putln("// Instead use one of the following constructors for '%s':", + u.SrcName()) + for _, field := range u.Fields { + c.Putln("// %s%sNew(%s %s) %s", u.SrcName(), field.SrcName(), + field.SrcName(), field.SrcType(), u.SrcName()) + } + + c.Putln("type %s struct {", u.SrcName()) + for _, field := range u.Fields { + field.Define(c) + } + c.Putln("}") + c.Putln("") + + // Write functions for each field that create instances of this + // union using the corresponding field. + u.New(c) + + // Write function that reads bytes and produces this union. + u.Read(c) + + // Write function that reads bytes and produces a list of this union. + u.ReadList(c) + + // Write function that writes bytes given this union. + u.Write(c) + + // Write function that writes a list of this union. + u.WriteList(c) +} + +func (u *Union) New(c *Context) { + for _, field := range u.Fields { + c.Putln("// %s%sNew constructs a new %s union type with the %s field.", + u.SrcName(), field.SrcName(), u.SrcName(), field.SrcName()) + c.Putln("func %s%sNew(%s %s) %s {", + u.SrcName(), field.SrcName(), field.SrcName(), + field.SrcType(), u.SrcName()) + c.Putln("var b int") + c.Putln("buf := make([]byte, %s)", u.Size()) + c.Putln("") + field.Write(c, "") + c.Putln("") + c.Putln("// Create the Union type") + c.Putln("v := %s{}", u.SrcName()) + c.Putln("") + c.Putln("// Now copy buf into all fields") + c.Putln("") + for _, field2 := range u.Fields { + c.Putln("b = 0 // always read the same bytes") + field2.Read(c, "v.") + c.Putln("") + } + c.Putln("return v") + c.Putln("}") + c.Putln("") + } +} + +func (u *Union) Read(c *Context) { + c.Putln("// %sRead reads a byte slice into a %s value.", + u.SrcName(), u.SrcName()) + c.Putln("func %sRead(buf []byte, v *%s) int {", u.SrcName(), u.SrcName()) + c.Putln("var b int") + c.Putln("") + for _, field := range u.Fields { + c.Putln("b = 0 // re-read the same bytes") + field.Read(c, "v.") + c.Putln("") + } + c.Putln("return %s", u.Size()) + c.Putln("}") + c.Putln("") +} + +func (u *Union) ReadList(c *Context) { + c.Putln("// %sReadList reads a byte slice into a list of %s values.", + u.SrcName(), u.SrcName()) + c.Putln("func %sReadList(buf []byte, dest []%s) int {", + u.SrcName(), u.SrcName()) + c.Putln("b := 0") + c.Putln("for i := 0; i < len(dest); i++ {") + c.Putln("dest[i] = %s{}", u.SrcName()) + c.Putln("b += %sRead(buf[b:], &dest[i])", u.SrcName()) + c.Putln("}") + c.Putln("return xgb.Pad(b)") + c.Putln("}") + c.Putln("") +} + +// This is a bit tricky since writing from a Union implies that only +// the data inside ONE of the elements is actually written. +// However, we only currently support unions where every field has the +// *same* *fixed* size. Thus, we make sure to always read bytes into +// every field which allows us to simply pick the first field and write it. +func (u *Union) Write(c *Context) { + c.Putln("// Bytes writes a %s value to a byte slice.", u.SrcName()) + c.Putln("// Each field in a union must contain the same data.") + c.Putln("// So simply pick the first field and write that to the wire.") + c.Putln("func (v %s) Bytes() []byte {", u.SrcName()) + c.Putln("buf := make([]byte, %s)", u.Size().Reduce("v.")) + c.Putln("b := 0") + c.Putln("") + u.Fields[0].Write(c, "v.") + c.Putln("return buf") + c.Putln("}") + c.Putln("") +} + +func (u *Union) WriteList(c *Context) { + c.Putln("// %sListBytes writes a list of %s values to a byte slice.", + u.SrcName(), u.SrcName()) + c.Putln("func %sListBytes(buf []byte, list []%s) int {", + u.SrcName(), u.SrcName()) + c.Putln("b := 0") + c.Putln("var unionBytes []byte") + c.Putln("for _, item := range list {") + c.Putln("unionBytes = item.Bytes()") + c.Putln("copy(buf[b:], unionBytes)") + c.Putln("b += xgb.Pad(len(unionBytes))") + c.Putln("}") + c.Putln("return b") + c.Putln("}") + c.Putln("") +} + +func (u *Union) WriteListSize(c *Context) { + c.Putln("// Union list size %s", u.SrcName()) + c.Putln("// %sListSize computes the size (bytes) of a list of %s values.", + u.SrcName()) + c.Putln("func %sListSize(list []%s) int {", u.SrcName(), u.SrcName()) + c.Putln("size := 0") + c.Putln("for _, item := range list {") + c.Putln("size += %s", u.Size().Reduce("item.")) + c.Putln("}") + c.Putln("return size") + c.Putln("}") + c.Putln("") +} diff --git a/nexgb/xgbgen/main.go b/nexgb/xgbgen/main.go new file mode 100644 index 0000000..fd5eac7 --- /dev/null +++ b/nexgb/xgbgen/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "flag" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" +) + +var ( + protoPath = flag.String("proto-path", + "/usr/share/xcb", "path to directory of X protocol XML files") + gofmt = flag.Bool("gofmt", true, + "When disabled, gofmt will not be run before outputting Go code") +) + +func usage() { + basename := os.Args[0] + if lastSlash := strings.LastIndex(basename, "/"); lastSlash > -1 { + basename = basename[lastSlash+1:] + } + log.Printf("Usage: %s [flags] xml-file", basename) + flag.PrintDefaults() + os.Exit(1) +} + +func init() { + log.SetFlags(0) +} + +func main() { + flag.Usage = usage + flag.Parse() + + if flag.NArg() != 1 { + log.Printf("A single XML protocol file can be processed at once.") + flag.Usage() + } + + // Read the single XML file into []byte + xmlBytes, err := ioutil.ReadFile(flag.Arg(0)) + if err != nil { + log.Fatal(err) + } + + // Initialize the buffer, parse it, and filter it through gofmt. + c := newContext() + c.Morph(xmlBytes) + + if !*gofmt { + c.out.WriteTo(os.Stdout) + } else { + cmdGofmt := exec.Command("gofmt") + cmdGofmt.Stdin = c.out + cmdGofmt.Stdout = os.Stdout + cmdGofmt.Stderr = os.Stderr + err = cmdGofmt.Run() + if err != nil { + log.Fatal(err) + } + } +} diff --git a/nexgb/xgbgen/misc.go b/nexgb/xgbgen/misc.go new file mode 100644 index 0000000..85d788f --- /dev/null +++ b/nexgb/xgbgen/misc.go @@ -0,0 +1,49 @@ +package main + +import ( + "regexp" + "strings" +) + +// AllCaps is a regex to test if a string identifier is made of +// all upper case letters. +var allCaps = regexp.MustCompile("^[A-Z0-9]+$") + +// popCount counts number of bits 'set' in mask. +func popCount(mask uint) uint { + m := uint32(mask) + n := uint(0) + for i := uint32(0); i < 32; i++ { + if m&(1<<i) != 0 { + n++ + } + } + return n +} + +// pad makes sure 'n' aligns on 4 bytes. +func pad(n int) int { + return (n + 3) & ^3 +} + +// splitAndTitle takes a string, splits it by underscores, capitalizes the +// first letter of each chunk, and smushes'em back together. +func splitAndTitle(s string) string { + // If the string is all caps, lower it and capitalize first letter. + if allCaps.MatchString(s) { + return strings.Title(strings.ToLower(s)) + } + + // If the string has no underscores, capitalize it and leave it be. + if i := strings.Index(s, "_"); i == -1 { + return strings.Title(s) + } + + // Now split the name at underscores, capitalize the first + // letter of each chunk, and smush'em back together. + chunks := strings.Split(s, "_") + for i, chunk := range chunks { + chunks[i] = strings.Title(strings.ToLower(chunk)) + } + return strings.Join(chunks, "") +} diff --git a/nexgb/xgbgen/protocol.go b/nexgb/xgbgen/protocol.go new file mode 100644 index 0000000..433f4e2 --- /dev/null +++ b/nexgb/xgbgen/protocol.go @@ -0,0 +1,70 @@ +package main + +import ( + "log" + "strings" +) + +// Protocol is a type that encapsulates all information about one +// particular XML file. It also contains links to other protocol types +// if this protocol imports other other extensions. The import relationship +// is recursive. +type Protocol struct { + Parent *Protocol + Name string + ExtXName string + ExtName string + MajorVersion string + MinorVersion string + + Imports []*Protocol + Types []Type + Requests []*Request +} + +type Protocols []*Protocol + +func (ps Protocols) Len() int { return len(ps) } +func (ps Protocols) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] } +func (ps Protocols) Less(i, j int) bool { return ps[i].ExtName < ps[j].ExtName } + +// Initialize traverses all structures, looks for 'Translation' type, +// and looks up the real type in the namespace. It also sets the source +// name for all relevant fields/structures. +// This is necessary because we don't traverse the XML in order initially. +func (p *Protocol) Initialize() { + for _, typ := range p.Types { + typ.Initialize(p) + } + for _, req := range p.Requests { + req.Initialize(p) + } +} + +// PkgName returns the name of this package. +// i.e., 'xproto' for the core X protocol, 'randr' for the RandR extension, etc. +func (p *Protocol) PkgName() string { + return strings.Replace(p.Name, "_", "", -1) +} + +// ProtocolGet searches the current context for the protocol with the given +// name. (i.e., the current protocol and its imports.) +// It is an error if one is not found. +func (p *Protocol) ProtocolFind(name string) *Protocol { + if p.Name == name { + return p // that was easy + } + for _, imp := range p.Imports { + if imp.Name == name { + return imp + } + } + log.Panicf("Could not find protocol with name '%s'.", name) + panic("unreachable") +} + +// isExt returns true if this protocol is an extension. +// i.e., it's name isn't "xproto". +func (p *Protocol) isExt() bool { + return strings.ToLower(p.Name) != "xproto" +} diff --git a/nexgb/xgbgen/request_reply.go b/nexgb/xgbgen/request_reply.go new file mode 100644 index 0000000..5032e31 --- /dev/null +++ b/nexgb/xgbgen/request_reply.go @@ -0,0 +1,152 @@ +package main + +import ( + "fmt" + "log" + "unicode" +) + +// Request represents all XML 'request' nodes. +// If the request doesn't have a reply, Reply is nil. +type Request struct { + srcName string // The Go name of this request. + xmlName string // The XML name of this request. + Opcode int + Combine bool // Not currently used. + Fields []Field // All fields in the request. + Reply *Reply // A reply, if one exists for this request. +} + +type Requests []*Request + +func (rs Requests) Len() int { return len(rs) } +func (rs Requests) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] } +func (rs Requests) Less(i, j int) bool { return rs[i].xmlName < rs[j].xmlName } + +// Initialize creates the proper Go source name for this request. +// It also initializes the reply if one exists, and all fields in this request. +func (r *Request) Initialize(p *Protocol) { + r.srcName = SrcName(p, r.xmlName) + if p.isExt() { + r.srcName = r.srcName + } + + if r.Reply != nil { + r.Reply.Initialize(p) + } + for _, field := range r.Fields { + field.Initialize(p) + } +} + +func (r *Request) SrcName() string { + return r.srcName +} + +func (r *Request) XmlName() string { + return r.xmlName +} + +// ReplyName gets the Go source name of the function that generates a +// reply type from a slice of bytes. +// The generated function is not currently exported. +func (r *Request) ReplyName() string { + if r.Reply == nil { + log.Panicf("Cannot call 'ReplyName' on request %s, which has no reply.", + r.SrcName()) + } + name := r.SrcName() + lower := string(unicode.ToLower(rune(name[0]))) + name[1:] + return fmt.Sprintf("%sReply", lower) +} + +// ReplyTypeName gets the Go source name of the type holding all reply data +// for this request. +func (r *Request) ReplyTypeName() string { + if r.Reply == nil { + log.Panicf("Cannot call 'ReplyName' on request %s, which has no reply.", + r.SrcName()) + } + return fmt.Sprintf("%sReply", r.SrcName()) +} + +// ReqName gets the Go source name of the function that generates a byte +// slice from a list of parameters. +// The generated function is not currently exported. +func (r *Request) ReqName() string { + name := r.SrcName() + lower := string(unicode.ToLower(rune(name[0]))) + name[1:] + return fmt.Sprintf("%sRequest", lower) +} + +// CookieName gets the Go source name of the type that holds cookies for +// this request. +func (r *Request) CookieName() string { + return fmt.Sprintf("%sCookie", r.SrcName()) +} + +// Size for Request needs a context. +// Namely, if this is an extension, we need to account for *four* bytes +// of a header (extension opcode, request opcode, and the sequence number). +// If it's a core protocol request, then we only account for *three* +// bytes of the header (remove the extension opcode). +func (r *Request) Size(c *Context) Size { + size := newFixedSize(0, true) + + // If this is a core protocol request, we squeeze in an extra byte of + // data (from the fields below) between the opcode and the size of the + // request. In an extension request, this byte is always occupied + // by the opcode of the request (while the first byte is always occupied + // by the opcode of the extension). + if !c.protocol.isExt() { + size = size.Add(newFixedSize(3, true)) + } else { + size = size.Add(newFixedSize(4, true)) + } + + for _, field := range r.Fields { + switch field := field.(type) { + case *LocalField: // local fields don't go over the wire + continue + case *SingleField: + fsz := field.Size() + if _, isstruct := field.Type.(*Struct); isstruct { + fsz.Expression = fsz.Expression.Specialize(field.SrcName()) + } + size = size.Add(fsz) + default: + size = size.Add(field.Size()) + } + } + return newExpressionSize(&Padding{ + Expr: size.Expression, + }, size.exact) +} + +// Reply encapsulates the fields associated with a 'reply' element. +type Reply struct { + Fields []Field +} + +// Size gets the number of bytes in this request's reply. +// A reply always has at least 7 bytes: +// 1 byte: A reply discriminant (first byte set to 1) +// 2 bytes: A sequence number +// 4 bytes: Number of additional bytes in 4-byte units past initial 32 bytes. +func (r *Reply) Size() Size { + size := newFixedSize(0, true) + + // Account for reply discriminant, sequence number and reply length + size = size.Add(newFixedSize(7, true)) + + for _, field := range r.Fields { + size = size.Add(field.Size()) + } + return size +} + +func (r *Reply) Initialize(p *Protocol) { + for _, field := range r.Fields { + field.Initialize(p) + } +} diff --git a/nexgb/xgbgen/size.go b/nexgb/xgbgen/size.go new file mode 100644 index 0000000..6e49371 --- /dev/null +++ b/nexgb/xgbgen/size.go @@ -0,0 +1,31 @@ +package main + +// Size corresponds to an expression that represents the number of bytes +// in some *thing*. Generally, sizes are used to allocate buffers and to +// inform X how big requests are. +// Size is basically a thin layer over an Expression that yields easy methods +// for adding and multiplying sizes. +type Size struct { + Expression + exact bool +} + +// newFixedSize creates a new Size with some fixed and known value. +func newFixedSize(fixed uint, exact bool) Size { + return Size{&Value{v: int(fixed)}, exact} +} + +// newExpressionSize creates a new Size with some expression. +func newExpressionSize(variable Expression, exact bool) Size { + return Size{variable, exact} +} + +// Add adds s1 and s2 and returns a new Size. +func (s1 Size) Add(s2 Size) Size { + return Size{newBinaryOp("+", s1, s2), s1.exact && s2.exact} +} + +// Multiply mupltiplies s1 and s2 and returns a new Size. +func (s1 Size) Multiply(s2 Size) Size { + return Size{newBinaryOp("*", s1, s2), s1.exact && s2.exact} +} diff --git a/nexgb/xgbgen/translation.go b/nexgb/xgbgen/translation.go new file mode 100644 index 0000000..d35fa88 --- /dev/null +++ b/nexgb/xgbgen/translation.go @@ -0,0 +1,427 @@ +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. + + Please see 'representation.go' for the type definitions that we're + translating to. +*/ + +import ( + "log" + "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)), + } + for _, field := range x.Fields { + if field.XMLName.Local == "doc" { + continue + } + ev.Fields = append(ev.Fields, field.Translate(ev)) + } + 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) + } + return err +} + +func (x *XMLErrorCopy) Translate() *ErrorCopy { + return &ErrorCopy{ + xmlName: x.Name, + Number: x.Number, + Old: newTranslation(x.Ref), + } +} + +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) + } + 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) + } + 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(), + } + for _, field := range x.Fields { + if field.XMLName.Local == "doc" || field.XMLName.Local == "fd" { + continue + } + r.Fields = append(r.Fields, field.Translate(r)) + } + + // 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)), + } + for _, field := range x.Fields { + if field.XMLName.Local == "doc" || field.XMLName.Local == "fd" { + continue + } + r.Fields = append(r.Fields, field.Translate(r)) + } + 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{}) 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), + } + 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, + } + case "switch": + swtch := &SwitchField{ + Name: x.Name, + Expr: x.Expr.Translate(), + Bitcases: make([]*Bitcase, len(x.Bitcases)), + } + 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) + } + 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) +} diff --git a/nexgb/xgbgen/type.go b/nexgb/xgbgen/type.go new file mode 100644 index 0000000..59f1a2d --- /dev/null +++ b/nexgb/xgbgen/type.go @@ -0,0 +1,390 @@ +package main + +import ( + "fmt" + "strings" +) + +type Type interface { + Initialize(p *Protocol) + SrcName() string + XmlName() string + Size() Size + + Define(c *Context) +} + +type Types []Type + +func (ts Types) Len() int { return len(ts) } +func (ts Types) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] } +func (ts Types) Less(i, j int) bool { + x1, x2 := ts[i].XmlName(), ts[j].XmlName() + s1, s2 := ts[i].SrcName(), ts[j].SrcName() + return (s1 == s2 && x1 < x2) || s1 < s2 +} + +// Translation is used *only* when transitioning from XML types to +// our better representation. They are placeholders for the real types (below) +// that will replace them. +type Translation struct { + xmlName string +} + +func newTranslation(name string) *Translation { + return &Translation{xmlName: name} +} + +// RealType takes 'XmlName' and finds its real concrete type in our Protocol. +// It is an error if we can't find such a type. +func (t *Translation) RealType(p *Protocol) Type { + // Check to see if there is a namespace. If so, strip it and use it to + // make sure we only look for a type in that protocol. + namespace, typeName := "", t.XmlName() + if ni := strings.Index(t.XmlName(), ":"); ni > -1 { + namespace, typeName = strings.ToLower(typeName[:ni]), typeName[ni+1:] + } + + if len(namespace) == 0 || namespace == strings.ToLower(p.Name) { + for _, typ := range p.Types { + if typeName == typ.XmlName() { + return typ + } + } + } + for _, imp := range p.Imports { + if len(namespace) == 0 || namespace == strings.ToLower(imp.Name) { + for _, typ := range imp.Types { + if typeName == typ.XmlName() { + return typ + } + } + } + } + panic("Could not find real type for translation type: " + t.XmlName()) +} + +func (t *Translation) SrcName() string { + panic("it is illegal to call SrcName on a translation type") +} + +func (t *Translation) XmlName() string { + return t.xmlName +} + +func (t *Translation) Size() Size { + panic("it is illegal to call Size on a translation type") +} + +func (t *Translation) Define(c *Context) { + panic("it is illegal to call Define on a translation type") +} + +func (t *Translation) Initialize(p *Protocol) { + panic("it is illegal to call Initialize on a translation type") +} + +type Base struct { + srcName string + xmlName string + size Size +} + +func (b *Base) SrcName() string { + return b.srcName +} + +func (b *Base) XmlName() string { + return b.xmlName +} + +func (b *Base) Size() Size { + return b.size +} + +func (b *Base) Initialize(p *Protocol) { + b.srcName = TypeSrcName(p, b) +} + +type Enum struct { + srcName string + xmlName string + Items []*EnumItem +} + +type EnumItem struct { + srcName string + xmlName string + Expr Expression +} + +func (enum *Enum) SrcName() string { + return enum.srcName +} + +func (enum *Enum) XmlName() string { + return enum.xmlName +} + +func (enum *Enum) Size() Size { + panic("Cannot take size of enum") +} + +func (enum *Enum) Initialize(p *Protocol) { + enum.srcName = TypeSrcName(p, enum) + for _, item := range enum.Items { + item.srcName = SrcName(p, item.xmlName) + if item.Expr != nil { + item.Expr.Initialize(p) + } + } +} + +type Resource struct { + srcName string + xmlName string +} + +func (r *Resource) SrcName() string { + return r.srcName +} + +func (r *Resource) XmlName() string { + return r.xmlName +} + +func (r *Resource) Size() Size { + return newFixedSize(BaseTypeSizes["Id"], true) +} + +func (r *Resource) Initialize(p *Protocol) { + r.srcName = TypeSrcName(p, r) +} + +type TypeDef struct { + srcName string + xmlName string + Old Type +} + +func (t *TypeDef) SrcName() string { + return t.srcName +} + +func (t *TypeDef) XmlName() string { + return t.xmlName +} + +func (t *TypeDef) Size() Size { + return t.Old.Size() +} + +func (t *TypeDef) Initialize(p *Protocol) { + t.Old = t.Old.(*Translation).RealType(p) + t.srcName = TypeSrcName(p, t) +} + +type Event struct { + srcName string + xmlName string + Number int + NoSequence bool + Fields []Field +} + +func (e *Event) SrcName() string { + return e.srcName +} + +func (e *Event) XmlName() string { + return e.xmlName +} + +func (e *Event) Size() Size { + return newExpressionSize(&Value{v: 32}, true) +} + +func (e *Event) Initialize(p *Protocol) { + e.srcName = TypeSrcName(p, e) + for _, field := range e.Fields { + field.Initialize(p) + } +} + +func (e *Event) EvType() string { + return fmt.Sprintf("%sEvent", e.srcName) +} + +type EventCopy struct { + srcName string + xmlName string + Old Type + Number int +} + +func (e *EventCopy) SrcName() string { + return e.srcName +} + +func (e *EventCopy) XmlName() string { + return e.xmlName +} + +func (e *EventCopy) Size() Size { + return newExpressionSize(&Value{v: 32}, true) +} + +func (e *EventCopy) Initialize(p *Protocol) { + e.srcName = TypeSrcName(p, e) + e.Old = e.Old.(*Translation).RealType(p) + if _, ok := e.Old.(*Event); !ok { + panic("an EventCopy's old type *must* be *Event") + } +} + +func (e *EventCopy) EvType() string { + return fmt.Sprintf("%sEvent", e.srcName) +} + +type Error struct { + srcName string + xmlName string + Number int + Fields []Field +} + +func (e *Error) SrcName() string { + return e.srcName +} + +func (e *Error) XmlName() string { + return e.xmlName +} + +func (e *Error) Size() Size { + return newExpressionSize(&Value{v: 32}, true) +} + +func (e *Error) Initialize(p *Protocol) { + e.srcName = TypeSrcName(p, e) + for _, field := range e.Fields { + field.Initialize(p) + } +} + +func (e *Error) ErrConst() string { + return fmt.Sprintf("Bad%s", e.srcName) +} + +func (e *Error) ErrType() string { + return fmt.Sprintf("%sError", e.srcName) +} + +type ErrorCopy struct { + srcName string + xmlName string + Old Type + Number int +} + +func (e *ErrorCopy) SrcName() string { + return e.srcName +} + +func (e *ErrorCopy) XmlName() string { + return e.xmlName +} + +func (e *ErrorCopy) Size() Size { + return newExpressionSize(&Value{v: 32}, true) +} + +func (e *ErrorCopy) Initialize(p *Protocol) { + e.srcName = TypeSrcName(p, e) + e.Old = e.Old.(*Translation).RealType(p) + if _, ok := e.Old.(*Error); !ok { + panic("an ErrorCopy's old type *must* be *Event") + } +} + +func (e *ErrorCopy) ErrConst() string { + return fmt.Sprintf("Bad%s", e.srcName) +} + +func (e *ErrorCopy) ErrType() string { + return fmt.Sprintf("%sError", e.srcName) +} + +type Struct struct { + srcName string + xmlName string + Fields []Field +} + +func (s *Struct) SrcName() string { + return s.srcName +} + +func (s *Struct) XmlName() string { + return s.xmlName +} + +func (s *Struct) Size() Size { + size := newFixedSize(0, true) + for _, field := range s.Fields { + size = size.Add(field.Size()) + } + return size +} + +func (s *Struct) Initialize(p *Protocol) { + s.srcName = TypeSrcName(p, s) + for _, field := range s.Fields { + field.Initialize(p) + } +} + +// HasList returns whether there is a field in this struct that is a list. +// When true, a more involved calculation is necessary to compute this struct's +// size. +func (s *Struct) HasList() bool { + for _, field := range s.Fields { + if _, ok := field.(*ListField); ok { + return true + } + } + return false +} + +type Union struct { + srcName string + xmlName string + Fields []Field +} + +func (u *Union) SrcName() string { + return u.srcName +} + +func (u *Union) XmlName() string { + return u.xmlName +} + +// Size for Union is broken. At least, it's broken for XKB. +// It *looks* like the protocol inherently relies on some amount of +// memory unsafety, since some members of unions in XKB are *variable* in +// length! The only thing I can come up with, maybe, is when a union has +// variable size, simply return the raw bytes. Then it's up to the user to +// pass those raw bytes into the appropriate New* constructor. GROSS! +// As of now, just pluck out the first field and return that size. This +// should work for union elements in randr.xml and xproto.xml. +func (u *Union) Size() Size { + return u.Fields[0].Size() +} + +func (u *Union) Initialize(p *Protocol) { + u.srcName = fmt.Sprintf("%sUnion", TypeSrcName(p, u)) + for _, field := range u.Fields { + field.Initialize(p) + } +} diff --git a/nexgb/xgbgen/xml.go b/nexgb/xgbgen/xml.go new file mode 100644 index 0000000..440d0a8 --- /dev/null +++ b/nexgb/xgbgen/xml.go @@ -0,0 +1,138 @@ +package main + +import ( + "encoding/xml" + "io/ioutil" + "log" +) + +type XML struct { + // Root 'xcb' element properties. + XMLName xml.Name `xml:"xcb"` + Header string `xml:"header,attr"` + ExtensionXName string `xml:"extension-xname,attr"` + ExtensionName string `xml:"extension-name,attr"` + MajorVersion string `xml:"major-version,attr"` + MinorVersion string `xml:"minor-version,attr"` + + // Types for all top-level elements. + // First are the simple ones. + Imports XMLImports `xml:"import"` + Enums []*XMLEnum `xml:"enum"` + Xids []*XMLXid `xml:"xidtype"` + XidUnions []*XMLXid `xml:"xidunion"` + TypeDefs []*XMLTypeDef `xml:"typedef"` + EventCopies []*XMLEventCopy `xml:"eventcopy"` + ErrorCopies []*XMLErrorCopy `xml:"errorcopy"` + + // Here are the complex ones, i.e., anything with "structure contents" + Structs []*XMLStruct `xml:"struct"` + Unions []*XMLUnion `xml:"union"` + Requests []*XMLRequest `xml:"request"` + Events []*XMLEvent `xml:"event"` + Errors []*XMLError `xml:"error"` +} + +type XMLImports []*XMLImport + +func (imports XMLImports) Eval() { + for _, imp := range imports { + xmlBytes, err := ioutil.ReadFile(*protoPath + "/" + imp.Name + ".xml") + if err != nil { + log.Fatalf("Could not read X protocol description for import "+ + "'%s' because: %s", imp.Name, err) + } + + imp.xml = &XML{} + err = xml.Unmarshal(xmlBytes, imp.xml) + if err != nil { + log.Fatal("Could not parse X protocol description for import "+ + "'%s' because: %s", imp.Name, err) + } + + // recursive imports... + imp.xml.Imports.Eval() + } +} + +type XMLImport struct { + Name string `xml:",chardata"` + xml *XML `xml:"-"` +} + +type XMLEnum struct { + Name string `xml:"name,attr"` + Items []*XMLEnumItem `xml:"item"` +} + +type XMLEnumItem struct { + Name string `xml:"name,attr"` + Expr *XMLExpression `xml:",any"` +} + +type XMLXid struct { + XMLName xml.Name + Name string `xml:"name,attr"` +} + +type XMLTypeDef struct { + Old string `xml:"oldname,attr"` + New string `xml:"newname,attr"` +} + +type XMLEventCopy struct { + Name string `xml:"name,attr"` + Number int `xml:"number,attr"` + Ref string `xml:"ref,attr"` +} + +type XMLErrorCopy struct { + Name string `xml:"name,attr"` + Number int `xml:"number,attr"` + Ref string `xml:"ref,attr"` +} + +type XMLStruct struct { + Name string `xml:"name,attr"` + Fields []*XMLField `xml:",any"` +} + +type XMLUnion struct { + Name string `xml:"name,attr"` + Fields []*XMLField `xml:",any"` +} + +type XMLRequest struct { + Name string `xml:"name,attr"` + Opcode int `xml:"opcode,attr"` + Combine bool `xml:"combine-adjacent,attr"` + Fields []*XMLField `xml:",any"` + Reply *XMLReply `xml:"reply"` +} + +type XMLReply struct { + Fields []*XMLField `xml:",any"` +} + +type XMLEvent struct { + Name string `xml:"name,attr"` + Number int `xml:"number,attr"` + NoSequence bool `xml:"no-sequence-number,attr"` + Fields []*XMLField `xml:",any"` +} + +type XMLError struct { + Name string `xml:"name,attr"` + Number int `xml:"number,attr"` + Fields []*XMLField `xml:",any"` +} + +type XMLExpression struct { + XMLName xml.Name + + Exprs []*XMLExpression `xml:",any"` + + Data string `xml:",chardata"` + Op string `xml:"op,attr"` + Ref string `xml:"ref,attr"` +} diff --git a/nexgb/xgbgen/xml_fields.go b/nexgb/xgbgen/xml_fields.go new file mode 100644 index 0000000..8b7b5c7 --- /dev/null +++ b/nexgb/xgbgen/xml_fields.go @@ -0,0 +1,86 @@ +package main + +import ( + "encoding/xml" + "log" +) + +type XMLField struct { + XMLName xml.Name + + // For 'pad' element + Bytes uint `xml:"bytes,attr"` + Align uint16 `xml:"align,attr"` + + // For 'field', 'list', 'localfield', 'exprfield' and 'switch' elements. + Name string `xml:"name,attr"` + + // For 'field', 'list', 'localfield', and 'exprfield' elements. + Type string `xml:"type,attr"` + + // For 'list', 'exprfield' and 'switch' elements. + Expr *XMLExpression `xml:",any"` + + // For 'valueparm' element. + ValueMaskType string `xml:"value-mask-type,attr"` + ValueMaskName string `xml:"value-mask-name,attr"` + ValueListName string `xml:"value-list-name,attr"` + + // For 'switch' element. + Bitcases []*XMLBitcase `xml:"bitcase"` + + // I don't know which elements these are for. The documentation is vague. + // They also seem to be completely optional. + OptEnum string `xml:"enum,attr"` + OptMask string `xml:"mask,attr"` + OptAltEnum string `xml:"altenum,attr"` +} + +// Bitcase represents a single expression followed by any number of fields. +// Namely, if the switch's expression (all bitcases are inside a switch), +// and'd with the bitcase's expression is equal to the bitcase expression, +// then the fields should be included in its parent structure. +// Note that since a bitcase is unique in that expressions and fields are +// siblings, we must exhaustively search for one of them. Essentially, +// it's the closest thing to a Union I can get to in Go without interfaces. +// Would an '<expression>' tag have been too much to ask? :-( +type XMLBitcase struct { + Fields []*XMLField `xml:",any"` + + // All the different expressions. + // When it comes time to choose one, use the 'Expr' method. + ExprOp *XMLExpression `xml:"op"` + ExprUnOp *XMLExpression `xml:"unop"` + ExprField *XMLExpression `xml:"fieldref"` + ExprValue *XMLExpression `xml:"value"` + ExprBit *XMLExpression `xml:"bit"` + ExprEnum *XMLExpression `xml:"enumref"` + ExprSum *XMLExpression `xml:"sumof"` + ExprPop *XMLExpression `xml:"popcount"` +} + +// Expr chooses the only non-nil Expr* field from Bitcase. +// Panic if there is more than one non-nil expression. +func (b *XMLBitcase) Expr() *XMLExpression { + choices := []*XMLExpression{ + b.ExprOp, b.ExprUnOp, b.ExprField, b.ExprValue, + b.ExprBit, b.ExprEnum, b.ExprSum, b.ExprPop, + } + + var choice *XMLExpression = nil + numNonNil := 0 + for _, c := range choices { + if c != nil { + numNonNil++ + choice = c + } + } + + if choice == nil { + log.Panicf("No top level expression found in a bitcase.") + } + if numNonNil > 1 { + log.Panicf("More than one top-level expression was found in a bitcase.") + } + return choice +} |