#!/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): 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)) 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) go(') {') else: go('func (c *Conn) %sRequest(', func_name) go_complex_writer_arguments(param_fields) go(') 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) go(') (*%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()