blob: 4d6d919efb31cd8d186f4c32807e229712c5073b [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2009 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
from xml.etree.cElementTree import *
from os.path import basename, exists
import getopt
import sys
import re
_ns = None
outfile = None
golines = []
def go(fmt, *args):
golines.append(fmt % args)
namere = re.compile('([A-Z0-9][a-z]+|[A-Z0-9]+(?![a-z])|[a-z]+)')
allcaps = re.compile('^[A-Z0-9]+$')
sizeoftab = {
"byte": 1,
"int8": 1,
"uint8": 1,
"int16": 2,
"uint16": 2,
"int32": 4,
"uint32": 4,
"float32": 4,
"float64": 8,
"Id": 4,
"Keysym": 4,
"Timestamp": 4,
}
def sizeof(t):
if t in sizeoftab:
return sizeoftab[t]
return 4
symbols = []
def readsymbols(filename):
symbols.append("XXX Dummy XXX")
if exists(filename):
for line in open(filename, 'r').readlines():
symbols.append(line.strip())
#
# Name munging crap for names, enums and types.
#
mangletab = {
"int8_t": "int8",
"uint8_t": "byte",
"uint16_t": "uint16",
"uint32_t": "uint32",
"int16_t": "int16",
"int32_t": "int32",
"float": "float32",
"double": "float64",
"char": "byte",
"void": "byte",
'VISUALTYPE': 'VisualInfo',
'DEPTH': 'DepthInfo',
'SCREEN': 'ScreenInfo',
'Setup': 'SetupInfo',
'WINDOW': 'Id',
}
def mangle(str):
if str in mangletab:
return mangletab[str]
return str
def camel(str):
return str[0].upper() + str[1:]
def uncamel(str):
return str[0].lower() + str[1:]
def nitem(str):
split = namere.finditer(str)
return ''.join([camel(match.group(0)) for match in split])
def titem(str):
str = mangle(str)
if str in sizeoftab:
return str
if allcaps.match(str):
return str.capitalize()
return nitem(str)
def n(list):
"Mangle name (JoinedCamelCase) and chop off 'xcb' prefix."
if len(list) == 1:
parts = [nitem(list[0])]
else:
parts = [nitem(x) for x in list[1:]]
return ''.join(parts)
def t(list):
"Mangle name (JoinedCamelCase) and chop off 'xcb' prefix. Preserve primitive type names."
if len(list) == 1:
return titem(list[0])
else:
parts = [titem(x) for x in list[1:]]
return ''.join(parts)
#
# Various helper functions
#
def go_type_setup(self, name, postfix):
'''
Sets up all the Go-related state by adding additional data fields to
all Field and Type objects. Here is where we figure out most of our
variable and function names.
Recurses into child fields and list member types.
'''
# Do all the various names in advance
self.c_type = t(name + postfix)
self.c_request_name = n(name)
self.c_reply_name = n(name + ('Reply',))
self.c_reply_type = t(name + ('Reply',))
if not self.is_container:
return
offset = 0
for field in self.fields:
go_type_setup(field.type, field.field_type, ())
if field.type.is_list:
go_type_setup(field.type.member, field.field_type, ())
field.c_field_type = t(field.field_type)
field.c_field_name = n((field.field_name,))
field.c_subscript = '[%d]' % field.type.nmemb if (field.type.nmemb > 1) else ''
field.c_pointer = ' ' if field.type.nmemb == 1 else '[]'
field.c_offset = offset
if field.type.fixed_size():
offset += field.type.size * field.type.nmemb
def go_accessor_length(expr, prefix, iswriting):
'''
Figures out what C code is needed to get a length field.
For fields that follow a variable-length field, use the accessor.
Otherwise, just reference the structure field directly.
'''
prefarrow = '' if prefix == '' else prefix + '.'
if expr.lenfield_name != None:
lenstr = prefarrow + n((expr.lenfield_name,))
if iswriting and lenstr.endswith("Len"):
# chop off ...Len and refer to len(array) instead
return "len(" + lenstr[:-3] + ")"
return "int(" + lenstr + ")"
else:
return str(expr.nmemb)
def go_accessor_expr(expr, prefix, iswriting):
'''
Figures out what C code is needed to get the length of a list field.
Recurses for math operations.
Returns bitcount for value-mask fields.
Otherwise, uses the value of the length field.
'''
lenexp = go_accessor_length(expr, prefix, iswriting)
if expr.op != None:
return '(' + go_accessor_expr(expr.lhs, prefix, iswriting) + ' ' + expr.op + ' ' + go_accessor_expr(expr.rhs, prefix, iswriting) + ')'
elif expr.bitfield:
return 'popCount(' + lenexp + ')'
else:
return lenexp
def go_complex(self, fieldlist=None):
'''
Helper function for handling all structure types.
Called for all structs, requests, replies, events, errors.
'''
if self.is_union:
go('type %s struct /*union */ {', self.c_type)
else:
go('type %s struct {', self.c_type)
if not fieldlist:
fieldlist = self.fields
for field in fieldlist:
if field.type.is_pad:
continue
if field.wire and field.type.fixed_size():
go(' %s %s%s', field.c_field_name, field.c_subscript, field.c_field_type)
if field.wire and not field.type.fixed_size():
go(' %s []%s', field.c_field_name, field.c_field_type)
go('}')
go('')
def go_get(dst, ofs, typename, typesize):
dst = "v." + dst
if typesize == 1:
if typename == 'byte':
go('%s = b[%s]', dst, ofs)
else:
go('%s = %s(b[%s])', dst, typename, ofs)
elif typesize == 2:
if typename == 'uint16':
go('%s = get16(b[%s:])', dst, ofs)
else:
go('%s = %s(get16(b[%s:]))', dst, typename, ofs)
elif typesize == 4:
if typename == 'uint32':
go('%s = get32(b[%s:])', dst, ofs)
else:
go('%s = %s(get32(b[%s:]))', dst, typename, ofs)
else:
go('get%s(b[%s:], &%s)', typename, ofs, dst)
def go_get_list(dst, ofs, typename, typesize, count):
if typesize == 1 and typename == 'byte':
go('copy(v.%s[0:%s], b[%s:])', dst, count, ofs)
else:
go('for i := 0; i < %s; i++ {', count)
go_get(dst + "[i]", ofs + "+i*" + str(typesize), typename, typesize)
go('}')
def go_complex_reader_help(self, fieldlist):
firstvar = 1
total = 0
for field in fieldlist:
fieldname = field.c_field_name
fieldtype = field.c_field_type
if field.wire and field.type.fixed_size():
total = field.c_offset + field.type.size * field.type.nmemb
if field.type.is_pad:
continue
if field.type.nmemb == 1:
go_get(fieldname, field.c_offset, fieldtype, field.type.size)
else:
go_get_list(fieldname, field.c_offset, fieldtype, field.type.size, field.type.nmemb)
if field.wire and not field.type.fixed_size():
lenstr = go_accessor_expr(field.type.expr, 'v', False)
if firstvar:
firstvar = 0
go('offset := %d', field.c_offset)
else:
go('offset = pad(offset)')
go('v.%s = make([]%s, %s)', fieldname, fieldtype, lenstr)
if fieldtype in sizeoftab:
go_get_list(fieldname, "offset", fieldtype, sizeoftab[fieldtype], "len(v."+fieldname+")")
go('offset += len(v.%s) * %d', fieldname, sizeoftab[fieldtype])
else:
go('for i := 0; i < %s; i++ {', lenstr)
go(' offset += get%s(b[offset:], &v.%s[i])', fieldtype, fieldname)
go('}')
if not firstvar:
return 'offset'
return str(total)
def go_complex_reader(self):
go('func get%s(b []byte, v *%s) int {', self.c_type, self.c_type)
go(' return %s', go_complex_reader_help(self, self.fields))
go('}')
go('')
def structsize(fieldlist):
fixedtotal = 0
for field in fieldlist:
if field.wire and field.type.fixed_size():
fixedtotal += field.type.size * field.type.nmemb
return fixedtotal
def go_put(src, ofs, typename, typesize):
if typesize == 1:
if typename == 'byte':
go('b[%s] = %s', ofs, src)
else:
go('b[%s] = byte(%s)', ofs, src)
elif typesize == 2:
if typename == 'uint16':
go('put16(b[%s:], %s)', ofs, src)
else:
go('put16(b[%s:], uint16(%s))', ofs, src)
elif typesize == 4:
if typename == 'uint32':
go('put32(b[%s:], %s)', ofs, src)
else:
go('put32(b[%s:], uint32(%s))', ofs, src)
else:
go('put%s(b[%s:], %s)', typename, ofs, src)
def go_complex_writer_help(fieldlist, prefix=''):
prefarrow = '' if prefix == '' else prefix + '.'
for field in fieldlist:
fieldname = prefarrow + field.c_field_name
fieldtype = field.c_field_type
if fieldname.endswith("Len"):
fieldname = "len(%s)" % fieldname[:-3]
fieldtype = "(exp)"
if not field.type.fixed_size():
continue
if field.type.is_expr:
expstr = go_accessor_expr(field.type.expr, prefix, True)
go_put(expstr, field.c_offset, "(exp)", field.type.size)
elif not field.type.is_pad:
if field.type.nmemb == 1:
go_put(fieldname, field.c_offset, fieldtype, field.type.size)
else:
go(' copy(b[%d:%d], %s)', field.c_offset, field.c_offset + field.type.nmemb, fieldname)
def go_complex_writer_arguments(param_fields, endstr):
out = []
for field in param_fields:
namestr = field.c_field_name
typestr = field.c_pointer + t(field.field_type)
if typestr == '[]byte' and namestr == 'Name':
typestr = 'string'
out.append(namestr + ' ' + typestr)
go(' ' + ', '.join(out) + ')' + endstr)
def go_complex_writer_arguments_names(param_fields):
out = []
for field in param_fields:
out.append(field.c_field_name)
return ', '.join(out)
def go_complex_writer(self, name, void):
func_name = self.c_request_name
param_fields = []
wire_fields = []
for field in self.fields:
if field.visible:
# _len is taken from the list directly
if not field.field_name.endswith("_len"):
# The field should appear as a call parameter
param_fields.append(field)
if field.wire and not field.auto:
# We need to set the field up in the structure
wire_fields.append(field)
if void:
go('func (c *Conn) %s(', func_name)
go_complex_writer_arguments(param_fields, "{")
else:
go('func (c *Conn) %sRequest(', func_name)
go_complex_writer_arguments(param_fields, "Cookie {")
fixedtotal = structsize(self.fields)
if fixedtotal <= 32:
go(' b := c.scratch[0:%d]', fixedtotal)
else:
go(' b := make([]byte, %d)', fixedtotal)
firstvar = 0
for field in wire_fields:
if not field.type.fixed_size():
if not firstvar:
firstvar = 1
go(' n := %d', fixedtotal)
go(' n += pad(%s * %d)', go_accessor_expr(field.type.expr, '', True), field.type.size)
if not firstvar:
go(' put16(b[2:], %d)', fixedtotal / 4)
else:
go(' put16(b[2:], uint16(n / 4))')
go(' b[0] = %s', self.opcode)
go_complex_writer_help(wire_fields)
if not void:
if firstvar:
go(' cookie := c.sendRequest(b)')
else:
go(' return c.sendRequest(b)')
else:
go(' c.sendRequest(b)')
# send extra data
for field in param_fields:
if not field.type.fixed_size():
if field.type.is_list:
fieldname = field.c_field_name
lenstr = go_accessor_expr(field.type.expr, '', True)
if t(field.field_type) == 'byte':
if fieldname == 'Name':
go(' c.sendString(%s)', fieldname)
else:
go(' c.sendBytes(%s[0:%s])', fieldname, lenstr)
elif t(field.field_type) == 'uint32':
go(' c.sendUInt32List(%s[0:%s])', fieldname, lenstr)
else:
go(' c.send%sList(%s, %s)', t(field.field_type), fieldname, lenstr)
if not void and firstvar:
go(' return cookie')
go('}')
go('')
if not void:
args = go_complex_writer_arguments_names(param_fields)
go('func (c *Conn) %s(', func_name)
go_complex_writer_arguments(param_fields, '(*%s, os.Error) {' % self.c_reply_type)
go(' return c.%sReply(c.%sRequest(%s))', func_name, func_name, args)
go('}')
go('')
#
# Struct definitions, readers and writers
#
def go_struct(self, name):
go_type_setup(self, name, ())
if symbols and t(name) not in symbols:
go('// excluding struct %s\n', t(name))
return
if self.c_type == 'SetupRequest': return
if self.c_type == 'SetupFailed': return
if self.c_type == 'SetupAuthenticate': return
go_complex(self)
go_complex_reader(self)
if self.c_type == 'Format': return
if self.c_type == 'VisualInfo': return
if self.c_type == 'DepthInfo': return
if self.c_type == 'SetupInfo': return
if self.c_type == 'ScreenInfo': return
# omit variable length struct writers, they're never used
if not self.fixed_size():
go('// omitting variable length send%s', self.c_type)
go('')
return
go('func (c *Conn) send%sList(list []%s, count int) {', self.c_type, self.c_type)
go(' b0 := make([]byte, %d * count)', structsize(self.fields))
go(' for k := 0; k < count; k++ {')
go(' b := b0[k * %d:]', structsize(self.fields))
go_complex_writer_help(self.fields, 'list[k]')
go(' }')
go(' c.sendBytes(b0)')
go('}')
go('')
def go_union(self, name):
pass
#
# Request writers with reply structs and readers where needed
#
def replyfields(self):
l = []
for field in self.fields:
if field.type.is_pad or not field.wire: continue
if field.field_name == 'response_type': continue
if field.field_name == 'sequence': continue
if field.field_name == 'length':
if self.c_reply_name != 'GetImageReply' and self.c_reply_name != 'GetKeyboardMappingReply':
continue
l.append(field)
return l
def go_reply(self, name):
'''
Declares the function that returns the reply structure.
'''
fields = replyfields(self.reply)
go_complex(self.reply, fields)
go('func (c *Conn) %s(cookie Cookie) (*%s, os.Error) {', self.c_reply_name, self.c_reply_type)
go(' b, error := c.waitForReply(cookie)')
go(' if error != nil { return nil, error }')
go(' v := new(%s)', self.c_reply_type)
go_complex_reader_help(self.reply, fields)
go(' return v, nil')
go('}')
go('')
def go_request(self, name):
'''
Exported function that handles request declarations.
'''
go_type_setup(self, name, ('Request',))
if symbols and n(name) not in symbols:
go('// excluding request %s\n', n(name))
return
if self.reply:
go_complex_writer(self, name, False)
go_type_setup(self.reply, name, ('Reply',))
go_reply(self, name)
else:
go_complex_writer(self, name, True)
#
# Event structs and readers
#
def eventfields(self):
l = []
for field in self.fields:
if field.type.is_pad or not field.wire: continue
if field.field_name == 'response_type': continue
if field.field_name == 'sequence': continue
l.append(field)
return l
eventlist = []
def dumpeventlist():
go('func parseEvent(buf []byte) (Event, os.Error) {')
go(' switch buf[0] {')
for event in eventlist:
go(' case %s: return get%sEvent(buf), nil', event, event)
go(' }')
go(' return nil, os.NewError("unknown event type")')
go('}')
def go_event(self, name):
'''
Exported function that handles event declarations.
'''
go_type_setup(self, name, ('Event',))
if symbols and t(name) not in symbols:
go('// excluding event %s\n', t(name))
return
eventlist.append(n(name))
go('const %s = %s', t(name), self.opcodes[name])
go('')
fields = eventfields(self)
if self.name == name:
# Structure definition
go_complex(self, fields)
go('func get%s(b []byte) %s {', self.c_type, self.c_type)
go(' var v %s', self.c_type)
go_complex_reader_help(self, fields)
go(' return v')
go('}')
go('')
else:
# maybe skip this depending on how it interacts with type switching on interfaces
go('type %s %s', n(name + ('Event',)), n(self.name + ('Event',)))
go('')
go('func get%s(b []byte) %s {', self.c_type, self.c_type)
go(' return (%s)(get%s(b))', n(name + ('Event',)), n(self.name + ('Event',)))
go('}')
go('')
#
# Map simple types to primitive types
#
def go_simple(self, name):
'''
Exported function that handles cardinal type declarations.
These are types which are typedef'd to one of the CARDx's, char, float, etc.
We stick them into the mangletab. Lop off xcb prefix.
'''
go_type_setup(self, name, ())
if self.name != name:
if _ns.is_ext:
name = name[2]
else:
name = name[1]
if name == "KEYSYM":
mangletab[name] = "Keysym"
elif name == "TIMESTAMP":
mangletab[name] = "Timestamp"
elif self.size == 4:
mangletab[name] = "Id"
else:
mangletab[name] = t(self.name)
#
# Dump enums as consts, calculate implicit values instead
# of using iota.
#
def go_enum(self, name):
if symbols and t(name) not in symbols:
go('// excluding enum %s\n', t(name))
return
go('const (')
iota = 0
for (enam, eval) in self.values:
if str(eval) == '':
iota = iota + 1
eval = iota
else:
iota = int(eval)
if name[1] == 'Atom':
s = name[1] + "".join([x.capitalize() for x in enam.split("_")])
else:
s = n(name + (enam,))
go(' %s = %s', s, eval)
go(')')
go('')
errorlist = []
def dumperrorlist():
go('var errorNames = map[byte]string{')
for error in errorlist:
go(' Bad%s: "%s",', error, error)
go('}')
go('')
def go_error(self, name):
'''
Exported function that handles error declarations.
'''
errorlist.append(n(name))
go('const Bad%s = %s', n(name), self.opcodes[name])
go('')
#
# Create the go file
#
def go_open(self):
'''
Exported function that handles module open.
Opens the files and writes out the auto-generated code.
'''
global _ns
_ns = self.namespace
go('// This file was generated automatically from %s.', _ns.file)
go('')
go('package xgb')
go('')
go('import "os"')
go('')
if _ns.is_ext:
go('const %s_MAJOR_VERSION = %s', _ns.ext_name.upper(), _ns.major_version)
go('const %s_MINOR_VERSION = %s', _ns.ext_name.upper(), _ns.minor_version)
go('')
def go_close(self):
'''
Exported function that handles module close.
'''
global outfile
if len(eventlist) > 0:
dumpeventlist()
if len(errorlist) > 0:
dumperrorlist()
if not outfile:
outfile = '%s.go' % _ns.header
gofile = open(outfile, 'w')
for line in golines:
gofile.write(line)
gofile.write('\n')
gofile.close()
# Main routine starts here
# Must create an "output" dictionary before any xcbgen imports.
output = {'open' : go_open,
'close' : go_close,
'simple' : go_simple,
'enum' : go_enum,
'struct' : go_struct,
'union' : go_union,
'request' : go_request,
'event' : go_event,
'error' : go_error
}
# Boilerplate below this point
# Check for the argument that specifies path to the xcbgen python package.
try:
opts, args = getopt.getopt(sys.argv[1:], 'p:s:o:')
except getopt.GetoptError, err:
print str(err)
print 'Usage: go_client.py [-p path] [-s symbol_list_file] [-o output.go] file.xml'
sys.exit(1)
for (opt, arg) in opts:
if opt == '-p':
sys.path.append(arg)
if opt == '-s':
readsymbols(arg)
if opt == '-o':
outfile = arg
# Import the module class
try:
from xcbgen.state import Module
except ImportError:
print 'Failed to load the xcbgen Python package!'
print 'Make sure that xcb/proto installed it on your Python path,'
print 'or pass the path with -p.'
print ''
raise
module = Module(args[0], output)
module.register()
module.resolve()
module.generate()