initial commit. not currently in a working state.

This commit is contained in:
Andrew Gallant (Ocelot) 2012-04-28 23:25:57 -04:00
commit 52a21b415a
16 changed files with 1906 additions and 0 deletions

18
nexgb/AUTHORS Normal file
View File

@ -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>

39
nexgb/CONTRIBUTORS Normal file
View File

@ -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>

42
nexgb/LICENSE Normal file
View File

@ -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.

19
nexgb/Makefile Normal file
View File

@ -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

33
nexgb/README Normal file
View File

@ -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

111
nexgb/auth.go Normal file
View File

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

484
nexgb/xgb.go Normal file
View File

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

103
nexgb/xgb_help.go Normal file
View File

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

89
nexgb/xgbgen/context.go Normal file
View File

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

255
nexgb/xgbgen/go.go Normal file
View File

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

64
nexgb/xgbgen/main.go Normal file
View File

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

44
nexgb/xgbgen/misc.go Normal file
View File

@ -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, "")
}

BIN
nexgb/xgbgen/xgbgen Executable file

Binary file not shown.

298
nexgb/xgbgen/xml.go Normal file
View File

@ -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"`
}

View File

@ -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 "&amp;":
return wrap(operand1.Eval() & operand2.Eval())
case "&lt;&lt;":
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")
}

147
nexgb/xgbgen/xml_fields.go Normal file
View File

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