diff options
Diffstat (limited to 'nexgb')
| -rw-r--r-- | nexgb/AUTHORS | 18 | ||||
| -rw-r--r-- | nexgb/CONTRIBUTORS | 39 | ||||
| -rw-r--r-- | nexgb/LICENSE | 42 | ||||
| -rw-r--r-- | nexgb/Makefile | 19 | ||||
| -rw-r--r-- | nexgb/README | 33 | ||||
| -rw-r--r-- | nexgb/auth.go | 111 | ||||
| -rw-r--r-- | nexgb/xgb.go | 484 | ||||
| -rw-r--r-- | nexgb/xgb_help.go | 103 | ||||
| -rw-r--r-- | nexgb/xgbgen/context.go | 89 | ||||
| -rw-r--r-- | nexgb/xgbgen/go.go | 255 | ||||
| -rw-r--r-- | nexgb/xgbgen/main.go | 64 | ||||
| -rw-r--r-- | nexgb/xgbgen/misc.go | 44 | ||||
| -rwxr-xr-x | nexgb/xgbgen/xgbgen | bin | 0 -> 2318165 bytes | |||
| -rw-r--r-- | nexgb/xgbgen/xml.go | 298 | ||||
| -rw-r--r-- | nexgb/xgbgen/xml_expression.go | 160 | ||||
| -rw-r--r-- | nexgb/xgbgen/xml_fields.go | 147 | 
16 files changed, 1906 insertions, 0 deletions
| diff --git a/nexgb/AUTHORS b/nexgb/AUTHORS new file mode 100644 index 0000000..08fc0cd --- /dev/null +++ b/nexgb/AUTHORS @@ -0,0 +1,18 @@ +Andrew Gallant is the maintainer of this fork. What follows is the original +list of authors for the x-go-binding. + +# This is the official list of XGB authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as +#	Name or Organization <email address> +# The email address is not required for organizations. + +# Please keep the list sorted. + +Anthony Martin <ality@pbrane.org> +Firmansyah Adiputra <frm.adiputra@gmail.com> +Google Inc. +Scott Lawrence <bytbox@gmail.com> +Tor Andersson <tor.andersson@gmail.com> diff --git a/nexgb/CONTRIBUTORS b/nexgb/CONTRIBUTORS new file mode 100644 index 0000000..46dc4b0 --- /dev/null +++ b/nexgb/CONTRIBUTORS @@ -0,0 +1,39 @@ +Andrew Gallant is the maintainer of this fork. What follows is the original +list of contributors for the x-go-binding. + +# This is the official list of people who can contribute +# (and typically have contributed) code to the XGB repository. +# The AUTHORS file lists the copyright holders; this file +# lists people.  For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# The submission process automatically checks to make sure +# that people submitting code are listed in this file (by email address). +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +#     http://code.google.com/legal/individual-cla-v1.0.html +#     http://code.google.com/legal/corporate-cla-v1.0.html +# +# The agreement for individuals can be filled out on the web. +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. + +# Names should be added to this file like so: +#     Name <email address> + +# Please keep the list sorted. + +Anthony Martin <ality@pbrane.org> +Firmansyah Adiputra <frm.adiputra@gmail.com> +Ian Lance Taylor <iant@golang.org> +Nigel Tao <nigeltao@golang.org> +Robert Griesemer <gri@golang.org> +Russ Cox <rsc@golang.org> +Scott Lawrence <bytbox@gmail.com> +Tor Andersson <tor.andersson@gmail.com> diff --git a/nexgb/LICENSE b/nexgb/LICENSE new file mode 100644 index 0000000..d99cd90 --- /dev/null +++ b/nexgb/LICENSE @@ -0,0 +1,42 @@ +// Copyright (c) 2009 The XGB Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +//    * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +//    * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +//    * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Subject to the terms and conditions of this License, Google hereby +// grants to You a perpetual, worldwide, non-exclusive, no-charge, +// royalty-free, irrevocable (except as stated in this section) patent +// license to make, have made, use, offer to sell, sell, import, and +// otherwise transfer this implementation of XGB, where such license +// applies only to those patent claims licensable by Google that are +// necessarily infringed by use of this implementation of XGB. If You +// institute patent litigation against any entity (including a +// cross-claim or counterclaim in a lawsuit) alleging that this +// implementation of XGB or a Contribution incorporated within this +// implementation of XGB constitutes direct or contributory patent +// infringement, then any patent licenses granted to You under this +// License for this implementation of XGB shall terminate as of the date +// such litigation is filed. diff --git a/nexgb/Makefile b/nexgb/Makefile new file mode 100644 index 0000000..041d20c --- /dev/null +++ b/nexgb/Makefile @@ -0,0 +1,19 @@ +XPROTO=/usr/share/xcb +all: xproto xinerama + +xproto: +	python2 go_client.py $(XPROTO)/xproto.xml +	gofmt -w xproto.go + +xinerama: +	python2 go_client.py $(XPROTO)/xinerama.xml +	gofmt -w xinerama.go + +randr: +	python2 go_client.py $(XPROTO)/randr.xml +	gofmt -w randr.go + +render: +	python2 go_client.py $(XPROTO)/render.xml +	gofmt -w render.go + diff --git a/nexgb/README b/nexgb/README new file mode 100644 index 0000000..f659e32 --- /dev/null +++ b/nexgb/README @@ -0,0 +1,33 @@ +BurntSushi's Fork +================= +I've forked the XGB repository from Google Code due to inactivty upstream. + +Much of the code has been rewritten in an effort to support thread safety +and multiple extensions. Namely, go_client.py has been thrown away in favor +of an xgbgen package. + +The biggest parts that *haven't* been rewritten by me are the connection and +authentication handshakes. They're inherently messy, and there's really no +reason to re-work them. + +I like to release my code under the WTFPL, but since I'm starting with someone +else's work, I'm leaving the original license/contributor/author information +in tact. + +I suppose I can legitimately release xgbgen under the WTFPL. + +What follows is the original README: + +XGB README +========== +XGB is the X protocol Go language Binding. + +It is the Go equivalent of XCB, the X protocol C-language Binding +(http://xcb.freedesktop.org/). + +Unless otherwise noted, the XGB source files are distributed +under the BSD-style license found in the LICENSE file. + +Contributions should follow the same procedure as for the Go project: +http://golang.org/doc/contribute.html + diff --git a/nexgb/auth.go b/nexgb/auth.go new file mode 100644 index 0000000..355afeb --- /dev/null +++ b/nexgb/auth.go @@ -0,0 +1,111 @@ +// Copyright 2009 The XGB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xgb + +import ( +	"bufio" +	"errors" +	"io" +	"os" +) + +func getU16BE(r io.Reader, b []byte) (uint16, error) { +	_, err := io.ReadFull(r, b[0:2]) +	if err != nil { +		return 0, err +	} +	return uint16(b[0])<<8 + uint16(b[1]), nil +} + +func getBytes(r io.Reader, b []byte) ([]byte, error) { +	n, err := getU16BE(r, b) +	if err != nil { +		return nil, err +	} +	if int(n) > len(b) { +		return nil, errors.New("bytes too long for buffer") +	} +	_, err = io.ReadFull(r, b[0:n]) +	if err != nil { +		return nil, err +	} +	return b[0:n], nil +} + +func getString(r io.Reader, b []byte) (string, error) { +	b, err := getBytes(r, b) +	if err != nil { +		return "", err +	} +	return string(b), nil +} + +// readAuthority reads the X authority file for the DISPLAY. +// If hostname == "" or hostname == "localhost", +// readAuthority uses the system's hostname (as returned by os.Hostname) instead. +func readAuthority(hostname, display string) (name string, data []byte, err error) { +	// b is a scratch buffer to use and should be at least 256 bytes long +	// (i.e. it should be able to hold a hostname). +	var b [256]byte + +	// As per /usr/include/X11/Xauth.h. +	const familyLocal = 256 + +	if len(hostname) == 0 || hostname == "localhost" { +		hostname, err = os.Hostname() +		if err != nil { +			return "", nil, err +		} +	} + +	fname := os.Getenv("XAUTHORITY") +	if len(fname) == 0 { +		home := os.Getenv("HOME") +		if len(home) == 0 { +			err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set") +			return "", nil, err +		} +		fname = home + "/.Xauthority" +	} + +	r, err := os.Open(fname) +	if err != nil { +		return "", nil, err +	} +	defer r.Close() + +	br := bufio.NewReader(r) +	for { +		family, err := getU16BE(br, b[0:2]) +		if err != nil { +			return "", nil, err +		} + +		addr, err := getString(br, b[0:]) +		if err != nil { +			return "", nil, err +		} + +		disp, err := getString(br, b[0:]) +		if err != nil { +			return "", nil, err +		} + +		name0, err := getString(br, b[0:]) +		if err != nil { +			return "", nil, err +		} + +		data0, err := getBytes(br, b[0:]) +		if err != nil { +			return "", nil, err +		} + +		if family == familyLocal && addr == hostname && disp == display { +			return name0, data0, nil +		} +	} +	panic("unreachable") +} diff --git a/nexgb/xgb.go b/nexgb/xgb.go new file mode 100644 index 0000000..7e209a7 --- /dev/null +++ b/nexgb/xgb.go @@ -0,0 +1,484 @@ +// Copyright 2009 The XGB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The XGB package implements the X11 core protocol. +// It is based on XCB: http://xcb.freedesktop.org/ +package xgb + +import ( +	"errors" +	"fmt" +	"io" +	"net" +	"os" +	"strconv" +	"strings" +	"sync" +) + +const ( +	readBuffer  = 100 +	writeBuffer = 100 +) + +// A Conn represents a connection to an X server. +// Only one goroutine should use a Conn's methods at a time. +type Conn struct { +	host          string +	conn          net.Conn +	nextId        Id +	nextCookie    uint16 +	cookies       map[uint16]*Cookie +	events        queue +	err           error +	display       string +	defaultScreen int +	scratch       [32]byte +	Setup         SetupInfo +	extensions    map[string]byte + +	requestChan       chan *Request +	requestCookieChan chan *Cookie +	replyChan         chan bool +	eventChan         chan bool +	errorChan         chan bool + +	newIdLock   sync.Mutex +	writeLock   sync.Mutex +	dequeueLock sync.Mutex +	cookieLock  sync.Mutex +	extLock     sync.Mutex +} + +// Id is used for all X identifiers, such as windows, pixmaps, and GCs. +type Id uint32 + +// Request is used to abstract the difference between a request +// that expects a reply and a request that doesn't expect a reply. +type Request struct { +	buf        []byte +	cookieChan chan *Cookie +} + +func newRequest(buf []byte, needsReply bool) *Request { +	req := &Request{ +		buf:        buf, +		cookieChan: nil, +	} +	if needsReply { +		req.cookieChan = make(chan *Cookie) +	} +	return req +} + +// Cookies are the sequence numbers used to pair replies up with their requests +type Cookie struct { +	id        uint16 +	replyChan chan []byte +	errorChan chan error +} + +func newCookie(id uint16) *Cookie { +	return &Cookie{ +		id:        id, +		replyChan: make(chan []byte, 1), +		errorChan: make(chan error, 1), +	} +} + +// Event is an interface that can contain any of the events returned by the server. +// Use a type assertion switch to extract the Event structs. +type Event interface{} + +// Error contains protocol errors returned to us by the X server. +type Error struct { +	Detail uint8 +	Major  uint8 +	Minor  uint16 +	Cookie uint16 +	Id     Id +} + +func (e *Error) Error() string { +	return fmt.Sprintf("Bad%s (major=%d minor=%d cookie=%d id=0x%x)", +		errorNames[e.Detail], e.Major, e.Minor, e.Cookie, e.Id) +} + +// NewID generates a new unused ID for use with requests like CreateWindow. +func (c *Conn) NewId() Id { +	c.newIdLock.Lock() +	defer c.newIdLock.Unlock() + +	id := c.nextId +	// TODO: handle ID overflow +	c.nextId++ +	return id +} + +// RegisterExtension adds the respective extension's major op code to +// the extensions map. +func (c *Conn) RegisterExtension(name string) error { +	nameUpper := strings.ToUpper(name) +	reply, err := c.QueryExtension(nameUpper) + +	switch { +	case err != nil: +		return err +	case !reply.Present: +		return errors.New(fmt.Sprintf("No extension named '%s' is present.", +			nameUpper)) +	} + +	c.extLock.Lock() +	c.extensions[nameUpper] = reply.MajorOpcode +	c.extLock.Unlock() + +	return nil +} + +// A simple queue used to stow away events. +type queue struct { +	data [][]byte +	a, b int +} + +func (q *queue) queue(item []byte) { +	if q.b == len(q.data) { +		if q.a > 0 { +			copy(q.data, q.data[q.a:q.b]) +			q.a, q.b = 0, q.b-q.a +		} else { +			newData := make([][]byte, (len(q.data)*3)/2) +			copy(newData, q.data) +			q.data = newData +		} +	} +	q.data[q.b] = item +	q.b++ +} + +func (q *queue) dequeue(c *Conn) []byte { +	c.dequeueLock.Lock() +	defer c.dequeueLock.Unlock() + +	if q.a < q.b { +		item := q.data[q.a] +		q.a++ +		return item +	} +	return nil +} + +// newWriteChan creates the channel required for writing to the net.Conn. +func (c *Conn) newRequestChannels() { +	c.requestChan = make(chan *Request, writeBuffer) +	c.requestCookieChan = make(chan *Cookie, 1) + +	go func() { +		for request := range c.requestChan { +			cookieNum := c.nextCookie +			c.nextCookie++ + +			if request.cookieChan != nil { +				cookie := newCookie(cookieNum) +				c.cookies[cookieNum] = cookie +				request.cookieChan <- cookie +			} +			if _, err := c.conn.Write(request.buf); err != nil { +				fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", err) +				close(c.requestChan) +				return +			} +		} +	}() +} + +// request is a buffered write to net.Conn. +func (c *Conn) request(buf []byte, needsReply bool) *Cookie { +	req := newRequest(buf, needsReply) +	c.requestChan <- req + +	if req.cookieChan != nil { +		cookie := <-req.cookieChan +		close(req.cookieChan) +		return cookie +	} +	return nil +} + +func (c *Conn) sendRequest(needsReply bool, bufs ...[]byte) *Cookie { +	if len(bufs) == 1 { +		return c.request(bufs[0], needsReply) +	} + +	total := make([]byte, 0) +	for _, buf := range bufs { +		total = append(total, buf...) +	} +	return c.request(total, needsReply) +} + +func (c *Conn) newReadChannels() { +	c.eventChan = make(chan bool, readBuffer) + +	onError := func() { +		panic("read error") +	} + +	go func() { +		for { +			buf := make([]byte, 32) +			if _, err := io.ReadFull(c.conn, buf); err != nil { +				fmt.Fprintf(os.Stderr, "x protocol read error: %s\n", err) +				onError() +				return +			} + +			switch buf[0] { +			case 0: +				err := &Error{ +					Detail: buf[1], +					Cookie: uint16(get16(buf[2:])), +					Id:     Id(get32(buf[4:])), +					Minor:  get16(buf[8:]), +					Major:  buf[10], +				} +				if cookie, ok := c.cookies[err.Cookie]; ok { +					cookie.errorChan <- err +				} else { +					fmt.Fprintf(os.Stderr, "x protocol error: %s\n", err) +				} +			case 1: +				seq := uint16(get16(buf[2:])) +				if _, ok := c.cookies[seq]; !ok { +					continue +				} + +				size := get32(buf[4:]) +				if size > 0 { +					bigbuf := make([]byte, 32+size*4, 32+size*4) +					copy(bigbuf[0:32], buf) +					if _, err := io.ReadFull(c.conn, bigbuf[32:]); err != nil { +						fmt.Fprintf(os.Stderr, +							"x protocol read error: %s\n", err) +						onError() +						return +					} +					c.cookies[seq].replyChan <- bigbuf +				} else { +					c.cookies[seq].replyChan <- buf +				} +			default: +				c.events.queue(buf) +				select { +				case c.eventChan <- true: +				default: +				} +			} +		} +	}() +} + +func (c *Conn) waitForReply(cookie *Cookie) ([]byte, error) { +	if cookie == nil { +		panic("nil cookie") +	} +	if _, ok := c.cookies[cookie.id]; !ok { +		panic("waiting for a cookie that will never come") +	} +	select { +	case reply := <-cookie.replyChan: +		return reply, nil +	case err := <-cookie.errorChan: +		return nil, err +	} +	panic("unreachable") +} + +// WaitForEvent returns the next event from the server. +// It will block until an event is available. +func (c *Conn) WaitForEvent() (Event, error) { +	for { +		if reply := c.events.dequeue(c); reply != nil { +			return parseEvent(reply) +		} +		if !<-c.eventChan { +			return nil, errors.New("Event channel has been closed.") +		} +	} +	panic("unreachable") +} + +// PollForEvent returns the next event from the server if one is available in the internal queue. +// It will not read from the connection, so you must call WaitForEvent to receive new events. +// Only use this function to empty the queue without blocking. +func (c *Conn) PollForEvent() (Event, error) { +	if reply := c.events.dequeue(c); reply != nil { +		return parseEvent(reply) +	} +	return nil, nil +} + +// Dial connects to the X server given in the 'display' string. +// If 'display' is empty it will be taken from os.Getenv("DISPLAY"). +// +// Examples: +//	Dial(":1")                 // connect to net.Dial("unix", "", "/tmp/.X11-unix/X1") +//	Dial("/tmp/launch-123/:0") // connect to net.Dial("unix", "", "/tmp/launch-123/:0") +//	Dial("hostname:2.1")       // connect to net.Dial("tcp", "", "hostname:6002") +//	Dial("tcp/hostname:1.0")   // connect to net.Dial("tcp", "", "hostname:6001") +func Dial(display string) (*Conn, error) { +	c, err := connect(display) +	if err != nil { +		return nil, err +	} + +	// Get authentication data +	authName, authData, err := readAuthority(c.host, c.display) +	noauth := false +	if err != nil { +		fmt.Fprintf(os.Stderr, "Could not get authority info: %v\n", err) +		fmt.Fprintf(os.Stderr, "Trying connection without authority info...\n") +		authName = "" +		authData = []byte{} +		noauth = true +	} + +	// Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1". +	if !noauth && (authName != "MIT-MAGIC-COOKIE-1" || len(authData) != 16) { +		return nil, errors.New("unsupported auth protocol " + authName) +	} + +	buf := make([]byte, 12+pad(len(authName))+pad(len(authData))) +	buf[0] = 0x6c +	buf[1] = 0 +	put16(buf[2:], 11) +	put16(buf[4:], 0) +	put16(buf[6:], uint16(len(authName))) +	put16(buf[8:], uint16(len(authData))) +	put16(buf[10:], 0) +	copy(buf[12:], []byte(authName)) +	copy(buf[12+pad(len(authName)):], authData) +	if _, err = c.conn.Write(buf); err != nil { +		return nil, err +	} + +	head := make([]byte, 8) +	if _, err = io.ReadFull(c.conn, head[0:8]); err != nil { +		return nil, err +	} +	code := head[0] +	reasonLen := head[1] +	major := get16(head[2:]) +	minor := get16(head[4:]) +	dataLen := get16(head[6:]) + +	if major != 11 || minor != 0 { +		return nil, errors.New(fmt.Sprintf("x protocol version mismatch: %d.%d", major, minor)) +	} + +	buf = make([]byte, int(dataLen)*4+8, int(dataLen)*4+8) +	copy(buf, head) +	if _, err = io.ReadFull(c.conn, buf[8:]); err != nil { +		return nil, err +	} + +	if code == 0 { +		reason := buf[8 : 8+reasonLen] +		return nil, errors.New(fmt.Sprintf("x protocol authentication refused: %s", string(reason))) +	} + +	getSetupInfo(buf, &c.Setup) + +	if c.defaultScreen >= len(c.Setup.Roots) { +		c.defaultScreen = 0 +	} + +	c.nextId = Id(c.Setup.ResourceIdBase) +	c.nextCookie = 1 +	c.cookies = make(map[uint16]*Cookie) +	c.events = queue{make([][]byte, 100), 0, 0} +	c.extensions = make(map[string]byte) + +	c.newReadChannels() +	c.newRequestChannels() +	return c, nil +} + +// Close closes the connection to the X server. +func (c *Conn) Close() { c.conn.Close() } + +func connect(display string) (*Conn, error) { +	if len(display) == 0 { +		display = os.Getenv("DISPLAY") +	} + +	display0 := display +	if len(display) == 0 { +		return nil, errors.New("empty display string") +	} + +	colonIdx := strings.LastIndex(display, ":") +	if colonIdx < 0 { +		return nil, errors.New("bad display string: " + display0) +	} + +	var protocol, socket string +	c := new(Conn) + +	if display[0] == '/' { +		socket = display[0:colonIdx] +	} else { +		slashIdx := strings.LastIndex(display, "/") +		if slashIdx >= 0 { +			protocol = display[0:slashIdx] +			c.host = display[slashIdx+1 : colonIdx] +		} else { +			c.host = display[0:colonIdx] +		} +	} + +	display = display[colonIdx+1 : len(display)] +	if len(display) == 0 { +		return nil, errors.New("bad display string: " + display0) +	} + +	var scr string +	dotIdx := strings.LastIndex(display, ".") +	if dotIdx < 0 { +		c.display = display[0:] +	} else { +		c.display = display[0:dotIdx] +		scr = display[dotIdx+1:] +	} + +	dispnum, err := strconv.Atoi(c.display) +	if err != nil || dispnum < 0 { +		return nil, errors.New("bad display string: " + display0) +	} + +	if len(scr) != 0 { +		c.defaultScreen, err = strconv.Atoi(scr) +		if err != nil { +			return nil, errors.New("bad display string: " + display0) +		} +	} + +	// Connect to server +	if len(socket) != 0 { +		c.conn, err = net.Dial("unix", socket+":"+c.display) +	} else if len(c.host) != 0 { +		if protocol == "" { +			protocol = "tcp" +		} +		c.conn, err = net.Dial(protocol, c.host+":"+strconv.Itoa(6000+dispnum)) +	} else { +		c.conn, err = net.Dial("unix", "/tmp/.X11-unix/X"+c.display) +	} + +	if err != nil { +		return nil, errors.New("cannot connect to " + display0 + ": " + err.Error()) +	} +	return c, nil +} diff --git a/nexgb/xgb_help.go b/nexgb/xgb_help.go new file mode 100644 index 0000000..adb97e0 --- /dev/null +++ b/nexgb/xgb_help.go @@ -0,0 +1,103 @@ +package xgb + +// getExtensionOpcode retrieves the extension opcode from the extensions map. +// If one doesn't exist, just return 0. An X error will likely result. +func (c *Conn) getExtensionOpcode(name string) byte { +	return c.extensions[name] +} + +func (c *Conn) bytesPadding(buf []byte) []byte { +	return append(buf, make([]byte, pad(len(buf))-len(buf))...) +} + +func (c *Conn) bytesString(str string) []byte { +	return c.bytesPadding([]byte(str)) +} + +func (c *Conn) bytesStrList(list []Str, length int) []byte { +	buf := make([]byte, 0) +	for _, str := range list { +		buf = append(buf, []byte(str.Name)...) +	} +	return c.bytesPadding(buf) +} + +func (c *Conn) bytesUInt32List(list []uint32) []byte { +	buf := make([]byte, len(list)*4) +	for i, item := range list { +		put32(buf[i*4:], item) +	} +	return c.bytesPadding(buf) +} + +func (c *Conn) bytesIdList(list []Id, length int) []byte { +	buf := make([]byte, length*4) +	for i, item := range list { +		put32(buf[i*4:], uint32(item)) +	} +	return c.bytesPadding(buf) +} + +// Pad a length to align on 4 bytes. +func pad(n int) int { return (n + 3) & ^3 } + +func put16(buf []byte, v uint16) { +	buf[0] = byte(v) +	buf[1] = byte(v >> 8) +} + +func put32(buf []byte, v uint32) { +	buf[0] = byte(v) +	buf[1] = byte(v >> 8) +	buf[2] = byte(v >> 16) +	buf[3] = byte(v >> 24) +} + +func get16(buf []byte) uint16 { +	v := uint16(buf[0]) +	v |= uint16(buf[1]) << 8 +	return v +} + +func get32(buf []byte) uint32 { +	v := uint32(buf[0]) +	v |= uint32(buf[1]) << 8 +	v |= uint32(buf[2]) << 16 +	v |= uint32(buf[3]) << 24 +	return v +} + +// Voodoo to count the number of bits set in a value list mask. +func popCount(mask0 int) int { +	mask := uint32(mask0) +	n := 0 +	for i := uint32(0); i < 32; i++ { +		if mask&(1<<i) != 0 { +			n++ +		} +	} +	return n +} + +// DefaultScreen returns the Screen info for the default screen, which is +// 0 or the one given in the display argument to Dial. +func (c *Conn) DefaultScreen() *ScreenInfo { return &c.Setup.Roots[c.defaultScreen] } + +// ClientMessageData holds the data from a client message, +// duplicated in three forms because Go doesn't have unions. +type ClientMessageData struct { +	Data8  [20]byte +	Data16 [10]uint16 +	Data32 [5]uint32 +} + +func getClientMessageData(b []byte, v *ClientMessageData) int { +	copy(v.Data8[:], b) +	for i := 0; i < 10; i++ { +		v.Data16[i] = get16(b[i*2:]) +	} +	for i := 0; i < 5; i++ { +		v.Data32[i] = get32(b[i*4:]) +	} +	return 20 +} diff --git a/nexgb/xgbgen/context.go b/nexgb/xgbgen/context.go new file mode 100644 index 0000000..e5acb12 --- /dev/null +++ b/nexgb/xgbgen/context.go @@ -0,0 +1,89 @@ +package main + +import ( +	"bytes" +	"encoding/xml" +	"fmt" +	"log" +	"strings" +) + +type Context struct { +	xml *XML +	out *bytes.Buffer +} + +func newContext() *Context { +	return &Context{ +		xml: &XML{}, +		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) +	} +} + +// TypePrefix searches the parsed XML for a type matching 'needle'. +// It then returns the appropriate prefix to be used in source code. +// Note that the core X protocol *is* a namespace, but does not have a prefix. +// Also note that you should probably check the BaseTypeMap and TypeMap +// before calling this function. +func (c *Context) TypePrefix(needle Type) string { +	// If this is xproto, quit. No prefixes needed. +	if c.xml.Header == "xproto" { +		return "" +	} + +	// First check for the type in the current namespace. +	if c.xml.HasType(needle) { +		return strings.Title(c.xml.Header) +	} + +	// Now check each of the imports... +	for _, imp := range c.xml.Imports { +		if imp.xml.Header != "xproto" && imp.xml.HasType(needle) { +			return strings.Title(imp.xml.Header) +		} +	} + +	return "" +} + +// Translate is the big daddy of them all. It takes in an XML byte slice +// and writes Go code to the 'out' buffer. +func (c *Context) Translate(xmlBytes []byte) { +	err := xml.Unmarshal(xmlBytes, c.xml) +	if err != nil { +		log.Fatal(err) +	} + +	// Parse all imports +	c.xml.Imports.Eval() + +	// Make sure all top level enumerations have expressions +	// (For when there are empty items.) +	c.xml.Enums.Eval() + +	// It's Morphin' Time! +	c.xml.Morph(c) + +	// for _, req := range c.xml.Requests {  +		// if req.Name != "CreateContext" && req.Name != "MakeCurrent" {  +			// continue  +		// }  +		// log.Println(req.Name)  +		// for _, field := range req.Fields {  +			// log.Println("\t", field.XMLName.Local, field.Type.Morph(c))  +		// }  +	// }  +} diff --git a/nexgb/xgbgen/go.go b/nexgb/xgbgen/go.go new file mode 100644 index 0000000..eb3f0fb --- /dev/null +++ b/nexgb/xgbgen/go.go @@ -0,0 +1,255 @@ +package main +/* +	To the best of my ability, these are all of the Go specific formatting +	functions. If I've designed xgbgen correctly, this should be only the +	place that you change things to generate code for a new language. + +	This file is organized as follows: + +	* Imports and helper variables. +	* Manual type and name override maps. +	* Helper morphing functions. +	* Morphing functions for each "unit". +	* Morphing functions for collections of "units". +*/ + +import ( +	"strings" +) + +/******************************************************************************/ +// Manual type and name overrides. +/******************************************************************************/ + +// 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", +} + +// 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{ } + +/******************************************************************************/ +// Helper functions that aide in morphing repetive constructs. +// i.e., "structure contents", expressions, type and identifier names, etc. +/******************************************************************************/ + +// Morph changes every TYPE (not names) into something suitable +// for your language. +func (typ Type) Morph(c *Context) string { +	t := string(typ) + +	// If this is a base type, then write the raw Go type. +	if newt, ok := BaseTypeMap[t]; ok { +		return newt +	} + +	// If it's in the type map, use that translation. +	if newt, ok := TypeMap[t]; ok { +		return newt +	} + +	// If it's a resource type, just use 'Id'. +	if c.xml.IsResource(typ) { +		return "Id" +	} + +	// 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 splitAndTitle(namespace) + splitAndTitle(rest) +	} + +	// Since there is no namespace, we need to look for a namespace +	// in the current context. +	return c.TypePrefix(typ) + splitAndTitle(t) +} + +// Morph changes every identifier (NOT type) into something suitable +// for your language. +func (name Name) Morph(c *Context) string { +	n := string(name) + +	// If it's in the name map, use that translation. +	if newn, ok := NameMap[n]; ok { +		return newn +	} + +	return splitAndTitle(n) +} + +/******************************************************************************/ +// Per element morphing. +// Below are functions that morph a single unit. +/******************************************************************************/ + +// Import morphing. +func (imp *Import) Morph(c *Context) { +	c.Putln("// import \"%s\"", imp.Name) +} + +// Enum morphing. +func (enum *Enum) Morph(c *Context) { +	c.Putln("const (") +	for _, item := range enum.Items { +		c.Putln("%s%s = %d", enum.Name.Morph(c), item.Name.Morph(c), +			item.Expr.Eval()) +	} +	c.Putln(")\n") +} + +// Xid morphing. +func (xid *Xid) Morph(c *Context) { +	// Don't emit anything for xid types for now. +	// We're going to force them all to simply be 'Id' +	// to avoid excessive type converting. +	// c.Putln("type %s Id", xid.Name.Morph(c))  +} + +// TypeDef morphing. +func (typedef *TypeDef) Morph(c *Context) { +	c.Putln("type %s %s", typedef.Old.Morph(c), typedef.New.Morph(c)) +} + +// Struct morphing. +func (strct *Struct) Morph(c *Context) { +} + +// Union morphing. +func (union *Union) Morph(c *Context) { +} + +// Request morphing. +func (request *Request) Morph(c *Context) { +} + +// Event morphing. +func (ev *Event) Morph(c *Context) { +} + +// EventCopy morphing. +func (evcopy *EventCopy) Morph(c *Context) { +} + +// Error morphing. +func (err *Error) Morph(c *Context) { +} + +// ErrorCopy morphing. +func (errcopy *ErrorCopy) Morph(c *Context) { +} + +/******************************************************************************/ +// Collection morphing. +// Below are functions that morph a collections of units. +// Most of these can probably remain unchanged, but they are useful if you +// need to group all of some "unit" in a single block or something. +/******************************************************************************/ +func (imports Imports) Morph(c *Context) { +	if len(imports) == 0 { +		return +	} + +	c.Putln("// Imports are not required for XGB since everything is in") +	c.Putln("// a single package. Still these may be useful for ") +	c.Putln("// reference purposes.") +	for _, imp := range imports { +		imp.Morph(c) +	} +} + +func (enums Enums) Morph(c *Context) { +	c.Putln("// Enums\n") +	for _, enum := range enums { +		enum.Morph(c) +	} +} + +func (xids Xids) Morph(c *Context) { +	c.Putln("// Xids\n") +	for _, xid := range xids { +		xid.Morph(c) +	} +} + +func (typedefs TypeDefs) Morph(c *Context) { +	c.Putln("// TypeDefs\n") +	for _, typedef := range typedefs { +		typedef.Morph(c) +	} +} + +func (strct Structs) Morph(c *Context) { +	c.Putln("// Structs\n") +	for _, typedef := range strct { +		typedef.Morph(c) +	} +} + +func (union Unions) Morph(c *Context) { +	c.Putln("// Unions\n") +	for _, typedef := range union { +		typedef.Morph(c) +	} +} + +func (request Requests) Morph(c *Context) { +	c.Putln("// Requests\n") +	for _, typedef := range request { +		typedef.Morph(c) +	} +} + +func (event Events) Morph(c *Context) { +	c.Putln("// Events\n") +	for _, typedef := range event { +		typedef.Morph(c) +	} +} + +func (evcopy EventCopies) Morph(c *Context) { +	c.Putln("// Event Copies\n") +	for _, typedef := range evcopy { +		typedef.Morph(c) +	} +} + +func (err Errors) Morph(c *Context) { +	c.Putln("// Errors\n") +	for _, typedef := range err { +		typedef.Morph(c) +	} +} + +func (errcopy ErrorCopies) Morph(c *Context) { +	c.Putln("// Error copies\n") +	for _, typedef := range errcopy { +		typedef.Morph(c) +	} +} + diff --git a/nexgb/xgbgen/main.go b/nexgb/xgbgen/main.go new file mode 100644 index 0000000..69579a4 --- /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.Translate(xmlBytes) + +	if !*gofmt { +		c.out.WriteTo(os.Stdout) +	} else { +		cmdGofmt := exec.Command("gofmt") +		cmdGofmt.Stdin = c.out +		cmdGofmt.Stdout = os.Stdout +		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..9adcf5d --- /dev/null +++ b/nexgb/xgbgen/misc.go @@ -0,0 +1,44 @@ +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 +} + +// 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, leave it be. +	if i := strings.Index(s, "_"); i == -1 { +		return 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/xgbgen b/nexgb/xgbgen/xgbgenBinary files differ new file mode 100755 index 0000000..ef33abc --- /dev/null +++ b/nexgb/xgbgen/xgbgen diff --git a/nexgb/xgbgen/xml.go b/nexgb/xgbgen/xml.go new file mode 100644 index 0000000..0f632b4 --- /dev/null +++ b/nexgb/xgbgen/xml.go @@ -0,0 +1,298 @@ +package main + +import ( +	"encoding/xml" +	"io/ioutil" +	"log" +	"time" +) + +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 Imports `xml:"import"` +	Enums Enums `xml:"enum"` +	Xids Xids `xml:"xidtype"` +	XidUnions Xids `xml:"xidunion"` +	TypeDefs TypeDefs `xml:"typedef"` +	EventCopies EventCopies `xml:"eventcopy"` +	ErrorCopies ErrorCopies `xml:"errorcopy"` + +	// Here are the complex ones, i.e., anything with "structure contents" +	Structs Structs `xml:"struct"` +	Unions Unions `xml:"union"` +	Requests Requests `xml:"request"` +	Events Events `xml:"event"` +	Errors Errors `xml:"error"` +} + +// Morph cascades down all of the XML and calls each type's corresponding +// Morph function with itself as an argument (the context). +func (x *XML) Morph(c *Context) { +	// Start the header... +	c.Putln("package xgb") +	c.Putln("/*") +	c.Putln("\tX protocol API for '%s.xml'.", c.xml.Header) +	c.Putln("\tThis file is automatically generated. Edit at your own peril!") +	c.Putln("\tGenerated on %s", +		time.Now().Format("Jan 2, 2006 at 3:04:05pm MST")) +	c.Putln("*/") +	c.Putln("") + +	x.Imports.Morph(c) +	c.Putln("") + +	x.Enums.Morph(c) +	c.Putln("") + +	x.Xids.Morph(c) +	c.Putln("") + +	x.XidUnions.Morph(c) +	c.Putln("") + +	x.TypeDefs.Morph(c) +	c.Putln("") + +	x.Structs.Morph(c) +	c.Putln("") + +	x.Unions.Morph(c) +	c.Putln("") + +	x.Requests.Morph(c) +	c.Putln("") + +	x.Events.Morph(c) +	c.Putln("") + +	x.Errors.Morph(c) +	c.Putln("") + +	x.EventCopies.Morph(c) +	c.Putln("") + +	x.ErrorCopies.Morph(c) +	c.Putln("") +} + +// IsResource returns true if the 'needle' type is a resource type. +// i.e., an "xid" +func (x *XML) IsResource(needle Type) bool { +	for _, xid := range x.Xids { +		if needle == xid.Name { +			return true +		} +	} +	for _, xidunion := range x.XidUnions { +		if needle == xidunion.Name { +			return true +		} +	} +	for _, imp := range x.Imports { +		if imp.xml.IsResource(needle) { +			return true +		} +	} +	return false +} + +// HasType returns true if the 'needle' type can be found in the protocol +// description represented by 'x'. +func (x *XML) HasType(needle Type) bool { +	for _, enum := range x.Enums { +		if needle == enum.Name { +			return true +		} +	} +	for _, xid := range x.Xids { +		if needle == xid.Name { +			return true +		} +	} +	for _, xidunion := range x.XidUnions { +		if needle == xidunion.Name { +			return true +		} +	} +	for _, typedef := range x.TypeDefs { +		if needle == typedef.New { +			return true +		} +	} +	for _, evcopy := range x.EventCopies { +		if needle == evcopy.Name { +			return true +		} +	} +	for _, errcopy := range x.ErrorCopies { +		if needle == errcopy.Name { +			return true +		} +	} +	for _, strct := range x.Structs { +		if needle == strct.Name { +			return true +		} +	} +	for _, union := range x.Unions { +		if needle == union.Name { +			return true +		} +	} +	for _, ev := range x.Events { +		if needle == ev.Name { +			return true +		} +	} +	for _, err := range x.Errors { +		if needle == err.Name { +			return true +		} +	} + +	return false +} + +type Name string + +type Type string + +type Imports []*Import + +func (imports Imports) 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) +		} +	} +} + +type Import struct { +	Name string `xml:",chardata"` +	xml *XML `xml:"-"` +} + +type Enums []Enum + +// Eval on the list of all enum types goes through and forces every enum +// item to have a valid expression. +// This is necessary because when an item is empty, it is defined to have +// the value of "one more than that of the previous item, or 0 for the first +// item". +func (enums Enums) Eval() { +	for _, enum := range enums { +		nextValue := uint(0) +		for _, item := range enum.Items { +			if item.Expr == nil { +				item.Expr = newValueExpression(nextValue) +				nextValue++ +			} else { +				nextValue = item.Expr.Eval() + 1 +			} +		} +	} +} + +type Enum struct { +	Name Type `xml:"name,attr"` +	Items []*EnumItem `xml:"item"` +} + +type EnumItem struct { +	Name Name `xml:"name,attr"` +	Expr *Expression `xml:",any"` +} + +type Xids []*Xid + +type Xid struct { +	XMLName xml.Name +	Name Type `xml:"name,attr"` +} + +type TypeDefs []*TypeDef + +type TypeDef struct { +	Old Type `xml:"oldname,attr"` +	New Type `xml:"newname,attr"` +} + +type EventCopies []*EventCopy + +type EventCopy struct { +	Name Type `xml:"name,attr"` +	Number string `xml:"number,attr"` +	Ref Type `xml:"ref,attr"` +} + +type ErrorCopies []*ErrorCopy + +type ErrorCopy struct { +	Name Type `xml:"name,attr"` +	Number string `xml:"number,attr"` +	Ref Type `xml:"ref,attr"` +} + +type Structs []*Struct + +type Struct struct { +	Name Type `xml:"name,attr"` +	Fields []*Field `xml:",any"` +} + +type Unions []*Union + +type Union struct { +	Name Type `xml:"name,attr"` +	Fields []*Field `xml:",any"` +} + +type Requests []*Request + +type Request struct { +	Name Type `xml:"name,attr"` +	Opcode int `xml:"opcode,attr"` +	Combine bool `xml:"combine-adjacent,attr"` +	Fields []*Field `xml:",any"` +	Reply *Reply `xml:"reply"` +} + +type Reply struct { +	Fields []*Field `xml:",any"` +} + +type Events []*Event + +type Event struct { +	Name Type `xml:"name,attr"` +	Number int `xml:"number,attr"` +	NoSequence bool `xml:"no-sequence-number,true"` +	Fields []*Field `xml:",any"` +} + +type Errors []*Error + +type Error struct { +	Name Type `xml:"name,attr"` +	Number int `xml:"number,attr"` +	Fields []*Field `xml:",any"` +} + diff --git a/nexgb/xgbgen/xml_expression.go b/nexgb/xgbgen/xml_expression.go new file mode 100644 index 0000000..dd32512 --- /dev/null +++ b/nexgb/xgbgen/xml_expression.go @@ -0,0 +1,160 @@ +package main + +import ( +	"encoding/xml" +	"fmt" +	"log" +	"strconv" +) + +type Expression struct { +	XMLName xml.Name + +	Exprs []*Expression `xml:",any"` + +	Data string `xml:",chardata"` +	Op string `xml:"op,attr"` +	Ref string `xml:"ref,attr"` +} + +func newValueExpression(v uint) *Expression { +	return &Expression{ +		XMLName: xml.Name{Local: "value"}, +		Data: fmt.Sprintf("%d", v), +	} +} + +// String is for debugging. For actual use, please use 'Morph'. +func (e *Expression) String() string { +	switch e.XMLName.Local { +	case "op": +		return fmt.Sprintf("(%s %s %s)", e.Exprs[0], e.Op, e.Exprs[1]) +	case "unop": +		return fmt.Sprintf("(%s (%s))", e.Op, e.Exprs[0]) +	case "popcount": +		return fmt.Sprintf("popcount(%s)", e.Exprs[0]) +	case "fieldref": +		fallthrough +	case "value": +		return fmt.Sprintf("%s", e.Data) +	case "bit": +		return fmt.Sprintf("(1 << %s)", e.Data) +	case "enumref": +		return fmt.Sprintf("%s%s", e.Ref, e.Data) +	case "sumof": +		return fmt.Sprintf("sum(%s)", e.Ref) +	default: +		log.Panicf("Unrecognized expression element: %s", e.XMLName.Local) +	} + +	panic("unreachable") +} + +// Eval is used to *attempt* to compute a concrete value for a particular +// expression. This is used in the initial setup to instantiate values for +// empty items in enums. +// We can't compute a concrete value for expressions that rely on a context, +// i.e., some field value. +func (e *Expression) Eval() uint { +	switch e.XMLName.Local { +	case "op": +		if len(e.Exprs) != 2 { +			log.Panicf("'op' found %d expressions; expected 2.", len(e.Exprs)) +		} +		return e.BinaryOp(e.Exprs[0], e.Exprs[1]).Eval() +	case "unop": +		if len(e.Exprs) != 1 { +			log.Panicf("'unop' found %d expressions; expected 1.", len(e.Exprs)) +		} +		return e.UnaryOp(e.Exprs[0]).Eval() +	case "popcount": +		if len(e.Exprs) != 1 { +			log.Panicf("'popcount' found %d expressions; expected 1.", +				len(e.Exprs)) +		} +		return popCount(e.Exprs[0].Eval()) +	case "value": +		val, err := strconv.Atoi(e.Data) +		if err != nil { +			log.Panicf("Could not convert '%s' in 'value' expression to int.", +				e.Data) +		} +		return uint(val) +	case "bit": +		bit, err := strconv.Atoi(e.Data) +		if err != nil { +			log.Panicf("Could not convert '%s' in 'bit' expression to int.", +				e.Data) +		} +		if bit < 0 || bit > 31 { +			log.Panicf("A 'bit' literal must be in the range [0, 31], but " + +				" is %d", bit) +		} +		return 1 << uint(bit) +	case "fieldref": +		log.Panicf("Cannot compute concrete value of 'fieldref' in " + +			"expression '%s'.", e) +	case "enumref": +		log.Panicf("Cannot compute concrete value of 'enumref' in " + +			"expression '%s'.", e) +	case "sumof": +		log.Panicf("Cannot compute concrete value of 'sumof' in " + +			"expression '%s'.", e) +	} + +	log.Panicf("Unrecognized tag '%s' in expression context. Expected one of " + +		"op, fieldref, value, bit, enumref, unop, sumof or popcount.", +		e.XMLName.Local) +	panic("unreachable") +} + +func (e *Expression) BinaryOp(operand1, operand2 *Expression) *Expression { +	if e.XMLName.Local != "op" { +		log.Panicf("Cannot perform binary operation on non-op expression: %s", +			e.XMLName.Local) +	} +	if len(e.Op) == 0 { +		log.Panicf("Cannot perform binary operation without operator for: %s", +			e.XMLName.Local) +	} + +	wrap := newValueExpression +	switch e.Op { +	case "+": +		return wrap(operand1.Eval() + operand2.Eval()) +	case "-": +		return wrap(operand1.Eval() + operand2.Eval()) +	case "*": +		return wrap(operand1.Eval() * operand2.Eval()) +	case "/": +		return wrap(operand1.Eval() / operand2.Eval()) +	case "&": +		return wrap(operand1.Eval() & operand2.Eval()) +	case "<<": +		return wrap(operand1.Eval() << operand2.Eval()) +	} + +	log.Panicf("Invalid binary operator '%s' for '%s' expression.", +		e.Op, e.XMLName.Local) +	panic("unreachable") +} + +func (e *Expression) UnaryOp(operand *Expression) *Expression { +	if e.XMLName.Local != "unop" { +		log.Panicf("Cannot perform unary operation on non-unop expression: %s", +			e.XMLName.Local) +	} +	if len(e.Op) == 0 { +		log.Panicf("Cannot perform unary operation without operator for: %s", +			e.XMLName.Local) +	} + +	switch e.Op { +	case "~": +		return newValueExpression(^operand.Eval()) +	} + +	log.Panicf("Invalid unary operator '%s' for '%s' expression.", +		e.Op, e.XMLName.Local) +	panic("unreachable") +} diff --git a/nexgb/xgbgen/xml_fields.go b/nexgb/xgbgen/xml_fields.go new file mode 100644 index 0000000..18be6e3 --- /dev/null +++ b/nexgb/xgbgen/xml_fields.go @@ -0,0 +1,147 @@ +package main +/* +	A series of fields should be taken as "structure contents", and *not* +	just the single 'field' elements. Namely, 'fields' subsumes 'field' +	elements. + +	More particularly, 'fields' corresponds to list, in order, of any of the +	follow elements: pad, field, list, localfield, exprfield, valueparm +	and switch. + +	Thus, the 'Field' type must contain the union of information corresponding +	to all aforementioned fields. + +	This would ideally be a better job for interfaces, but I could not figure +	out how to make them jive with Go's XML package. (And I don't really feel +	up to type translation.) +*/ + +import ( +	"encoding/xml" +	"fmt" +	"log" +	"strings" +) + +type Field struct { +	XMLName xml.Name + +	// For 'pad' element +	Bytes int `xml:"bytes,attr"` + +	// For 'field', 'list', 'localfield', 'exprfield' and 'switch' elements. +	Name string `xml:"name,attr"` + +	// For 'field', 'list', 'localfield', and 'exprfield' elements. +	Type Type `xml:"type,attr"` + +	// For 'list', 'exprfield' and 'switch' elements. +	Expr *Expression `xml:",any"` + +	// For 'valueparm' element. +	ValueMaskType Type `xml:"value-mask-type,attr"` +	ValueMaskName string `xml:"value-mask-name,attr"` +	ValueListName string `xml:"value-list-name,attr"` + +	// For 'switch' element. +	Bitcases []*Bitcase `xml:"bitcase"` + +	// I don't know which elements these are for. The documentation is vague. +	// They also seem to be completely optional. +	OptEnum Type `xml:"enum,attr"` +	OptMask Type `xml:"mask,attr"` +	OptAltEnum Type `xml:"altenum,attr"` +} + +// String is for debugging purposes. +func (f *Field) String() string { +	switch f.XMLName.Local { +	case "pad": +		return fmt.Sprintf("pad (%d bytes)", f.Bytes) +	case "field": +		return fmt.Sprintf("field (type = '%s', name = '%s')", f.Type, f.Name) +	case "list": +		return fmt.Sprintf("list (type = '%s', name = '%s', length = '%s')", +			f.Type, f.Name, f.Expr) +	case "localfield": +		return fmt.Sprintf("localfield (type = '%s', name = '%s')", +			f.Type, f.Name) +	case "exprfield": +		return fmt.Sprintf("exprfield (type = '%s', name = '%s', expr = '%s')", +			f.Type, f.Name, f.Expr) +	case "valueparam": +		return fmt.Sprintf("valueparam (type = '%s', name = '%s', list = '%s')", +			f.ValueMaskType, f.ValueMaskName, f.ValueListName) +	case "switch": +		bitcases := make([]string, len(f.Bitcases)) +		for i, bitcase := range f.Bitcases { +			bitcases[i] = bitcase.StringPrefix("\t") +		} +		return fmt.Sprintf("switch (name = '%s', expr = '%s')\n\t%s", +			f.Name, f.Expr, strings.Join(bitcases, "\n\t")) +	default: +		log.Panicf("Unrecognized field element: %s", f.XMLName.Local) +	} + +	panic("unreachable") +} + +// 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 Bitcase struct { +	Fields []*Field `xml:",any"` + +	// All the different expressions. +	// When it comes time to choose one, use the 'Expr' method. +	ExprOp *Expression `xml:"op"` +	ExprUnOp *Expression `xml:"unop"` +	ExprField *Expression `xml:"fieldref"` +	ExprValue *Expression `xml:"value"` +	ExprBit *Expression `xml:"bit"` +	ExprEnum *Expression `xml:"enumref"` +	ExprSum *Expression `xml:"sumof"` +	ExprPop *Expression `xml:"popcount"` +} + +// StringPrefix is for debugging purposes only. +// StringPrefix takes a string to prefix to every extra line for formatting. +func (b *Bitcase) StringPrefix(prefix string) string { +	fields := make([]string, len(b.Fields)) +	for i, field := range b.Fields { +		fields[i] = fmt.Sprintf("%s%s", prefix, field) +	} +	return fmt.Sprintf("%s\n\t%s%s", b.Expr(), prefix, +		strings.Join(fields, "\n\t")) +} + +// Expr chooses the only non-nil Expr* field from Bitcase. +// Panic if there is more than one non-nil expression. +func (b *Bitcase) Expr() *Expression { +	choices := []*Expression{ +		b.ExprOp, b.ExprUnOp, b.ExprField, b.ExprValue, +		b.ExprBit, b.ExprEnum, b.ExprSum, b.ExprPop, +	} + +	var choice *Expression = 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 +} | 
