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