summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOri Bernstein <ori@eigenstate.org>2019-01-11 12:07:44 -0800
committerOri Bernstein <ori@eigenstate.org>2019-01-11 12:07:44 -0800
commit4212e61603d2a270ab839bd00c9a47a5ee241913 (patch)
treed51b992581690141aac24ffccaca028ee56c5eac
parente1eab19ea317e875334d7836613abf843281c941 (diff)
downloadlibxmyrb-4212e61603d2a270ab839bd00c9a47a5ee241913.tar.gz
Add rough first cut at doc generation.
-rw-r--r--gendoc.py401
1 files changed, 401 insertions, 0 deletions
diff --git a/gendoc.py b/gendoc.py
new file mode 100644
index 0000000..e1c7057
--- /dev/null
+++ b/gendoc.py
@@ -0,0 +1,401 @@
+from lxml import etree
+import getopt
+import os
+import sys
+import errno
+import re
+import io
+import subprocess
+import collections
+
+lentag = 0
+isextension = False
+indents = collections.defaultdict(int)
+tymap = {
+ 'char': 'byte',
+ 'BYTE': 'byte',
+ 'INT8': 'int8',
+ 'INT16': 'int16',
+ 'INT32': 'int32',
+ 'INT64': 'int64',
+ 'CARD8': 'byte',
+ 'CARD16': 'uint16',
+ 'CARD32': 'uint32',
+ 'CARD64': 'uint64',
+ 'BOOL': 'bool',
+ 'xid': 'xid',
+ 'void': 'void',
+}
+
+renames = {
+ 'render': {
+ 'CreateCursor': 'RenderCreateCursor'
+ }
+}
+
+def writeln(f, s, *args):
+ s = s.format(*args)
+ if len(s) == 0:
+ f.write('\n')
+ for ln in s.splitlines():
+ if len(ln):
+ f.write('\t'*indents[f] + ln + '\n')
+ else:
+ f.write('\n')
+
+def indent(f):
+ indents[f] += 1
+
+def outdent(f):
+ indents[f] -= 1
+
+def myrtype(ty):
+ if ty in tymap:
+ return tymap[ty]
+ else:
+ return ty.lower()
+
+def isprimitive(ty):
+ return ty in tymap
+
+def load(mod, path):
+ with open(path) as f:
+ t = etree.parse(f)
+
+ elts = collections.defaultdict(dict)
+ for e in t.getroot():
+ if e.tag is etree.Comment:
+ continue
+ name = e.get('name')
+ if mod in renames and name in renames[mod]:
+ print('rename {} => {}'.format(name, renames[mod][name]))
+ e.set('name', renames[mod][name])
+ if e.tag == 'enum': elts['enums'][e.get('name')] = e
+ elif e.tag == 'error': elts['errors'][e.get('name')] = e
+ elif e.tag == 'errorcopy': elts['errors'][e.get('name')] = e
+ elif e.tag == 'event': elts['events'][e.get('name')] = e
+ elif e.tag == 'eventcopy': elts['events'][e.get('name')] = e
+ elif e.tag == 'struct': elts['structs'][e.get('name')] = e
+ elif e.tag == 'request': elts['requests'][e.get('name')] = e
+ elif e.tag == 'union': elts['unions'][e.get('name')] = e
+ elif e.tag == 'import': elts['imports'][e.text] = e.text
+ elif e.tag == 'xidtype': tymap[e.get('name')] = tymap['xid']
+ elif e.tag == 'xidunion': tymap[e.get('name')] = tymap['xid']
+ elif e.tag == 'typedef': tymap[e.get('newname')] = myrtype(e.get('oldname'))
+ elif e.tag == 'generate': raise Exception('unimplemented')
+ else:
+ raise Exception('unknown tag {}'.format(e.tag))
+ return elts
+
+def enumval(e):
+ if e.tag == 'bit':
+ return 1 << int(e.text)
+ elif e.tag == 'value':
+ return int(e.text)
+ else:
+ print etree.tostring(e)
+ raise Exception('unknown tag {}'.format(e.tag))
+
+def genenum(f, name, elts):
+ ename = name.lower()
+ writeln(f, 'type {} = int32', ename)
+ for elt in elts:
+ if elt.tag is etree.Comment:
+ continue
+ if elt.tag == 'item':
+ val = enumval(elt[0])
+ writeln(f, 'const {}{}\t: {} = {}',
+ name, elt.get('name'), ename, val)
+ writeln(f, '')
+
+def lenexpr(elt):
+ if elt.tag == 'fieldref':
+ return '(val.{} : std.size)'.format(elt.text)
+ elif elt.tag == 'op':
+ return '({} {} {})'.format(
+ lenexpr(elt[0]), elt.get('op'), lenexpr(elt[1]))
+ elif elt.tag == 'value':
+ return elt.text
+ else:
+ print etree.tostring(elt)
+ raise Exception('unknown tag {}'.format(elt.tag))
+
+def fieldname(elt):
+ name = elt.get('name')
+ if name == 'type':
+ name = 'kind'
+ return name
+
+def genstructif(f, elt, suff=''):
+ if elt.tag == 'field':
+ writeln(f, '{}\t: {}{}',
+ fieldname(elt), myrtype(elt.get('type')), suff)
+ elif elt.tag == 'list':
+ etype = elt.get('type')
+ if etype == 'void':
+ etype = 'BYTE'
+ listlen = ':'
+ if elt.find('value') is not None:
+ listlen = elt.find('value').text
+ writeln(f, '{}\t: {}[{}]{}',
+ fieldname(elt), myrtype(etype), listlen, suff)
+ elif elt.tag == 'pad':
+ pass
+ elif elt.tag == 'switch':
+ for case in elt.findall('bitcase'):
+ if case.find('field') is None:
+ print etree.tostring(case)
+ raise Exception('unknown tag {}'.format(elt.tag))
+ writeln(f, '{}\t: {}',
+ fieldname(case.find('field')), myrtype(case.find('field').get('type')))
+ elif elt.tag == 'exprfield':
+ # synthesized, we can ignore for now
+ pass
+ else:
+ print etree.tostring(elt)
+ raise Exception('unknown tag {}'.format(elt.tag))
+
+def packfirst(elts, isreq, isevent):
+ if isreq or (isevent and not elts.get('xge')):
+ for (idx, elt) in enumerate(elts):
+ if elt.tag is etree.Comment or elt.tag in {'reply', 'doc'}:
+ continue
+ if elt.tag == 'pad' and elt.get('bytes') == '1':
+ return (True, elt, elts[idx+1:])
+ if elt.tag == 'field':
+ if myrtype(elt.get('type')) in {'byte', 'int8', 'uint8', 'bool'}:
+ return (True, elt, elts[idx+1:])
+ return (False, None, elts)
+
+def genstruct(f, name, selt, isreq=False, isresp=False, isevent=False):
+ suff = ''
+ if isreq:
+ suff = 'req'
+ elif isresp:
+ suff = 'resp'
+ (pack, first, elts) = packfirst(selt, isreq, isevent)
+ sname = name.lower()
+ writeln(f, 'type {}{} = struct', sname, suff)
+ # generate interface
+ indent(f)
+ if isreq:
+ writeln(f, 'major\t: byte')
+ if pack:
+ genstructif(f, first)
+ else:
+ writeln(f, 'minor\t: byte')
+ writeln(f, 'length\t: uint16')
+ elif isresp:
+ writeln(f, 'response_type\t: byte')
+ writeln(f, 'sequence\t: uint16')
+ writeln(f, 'length\t: uint32')
+ elif isevent:
+ writeln(f, 'issent\t: bool')
+ if selt.get('xge') is not None:
+ writeln(f, 'ext\t: byte')
+ writeln(f, 'sequence\t:uint16')
+ writeln(f, 'length\t: uint32')
+ writeln(f, 'event_type\t: uint16')
+ else:
+ if pack:
+ genstructif(f, first)
+ if selt.get('no-sequence-number') is None:
+ writeln(f, 'sequence\t:uint16')
+
+ for elt in elts:
+ if elt.tag is etree.Comment or elt.tag in {'reply', 'doc'}:
+ continue
+ genstructif(f, elt)
+
+
+ outdent(f)
+ writeln(f, ';;')
+ writeln(f, '')
+
+ sw = selt.find('switch')
+ if sw is not None:
+ writeln(f, 'const No{}vals = (0 : {}vals#)', sname, sname)
+ writeln(f, 'type {}vals = struct', sname)
+ indent(f)
+ for elt in sw.findall('bitcase'):
+ if elt.tag is etree.Comment or elt.tag in {'reply', 'doc'}:
+ continue
+ genstructif(f, elt.find('field'))
+ outdent(f)
+ writeln(f, ';;')
+ writeln(f, '')
+ writeln(f, '')
+
+
+def eltsz(elt):
+ return 20
+
+
+# ugh, C style
+def genunion(f, name, elts, suff=''):
+ sz = 0
+ uname = name.lower()
+ writeln(f, 'type {} = struct', uname)
+ indent(f)
+ for u in elts:
+ if u.tag is etree.Comment:
+ continue
+ genstructif(f, u, '[...]')
+ sz = max(sz, eltsz(u))
+ outdent(f)
+ writeln(f, ';;')
+ writeln(f, '')
+
+ writeln(f, 'const pack{}{} : (c : display#, val : {}{}# -> void)', uname, suff, uname, suff)
+ writeln(f, 'const unpack{}{} : (buf : byte[:], off : std.size, val : {}{}# -> std.size)', uname, suff, uname, suff)
+ writeln(f, '')
+
+
+def gencallproto(f, name, elts):
+ f.write(indents[summary]*'\t')
+ f.write('const {} : (dpy : display#'.format(name.lower()))
+ resp = elts.find('reply')
+ for elt in elts:
+ if elt.tag is etree.Comment or elt.tag in {'pad', 'reply', 'doc', 'reply', 'exprfield'}:
+ continue
+
+ f.write(', ')
+ if elt.tag == 'field':
+ f.write('{} : {}'.format(
+ fieldname(elt), myrtype(elt.get('type'))))
+ elif elt.tag == 'list':
+ etype = elt.get('type')
+ if etype == 'void':
+ etype = 'BYTE'
+ f.write('{}: {}[:]'.format(
+ fieldname(elt), myrtype(etype)))
+ elif elt.tag == 'switch':
+ f.write('vals : {}vals#'.format(name.lower()))
+ elif elt.tag == 'exprfield':
+ # synthesized, we can ignore for now
+ pass
+ else:
+ print etree.tostring(elt)
+ raise Exception('unknown tag {}'.format(elt.tag))
+ if resp is not None:
+ writeln(f, '-> {}seq)\n', name.lower())
+ writeln(f, 'const wait{} : (dpy : display#, tag : {}seq -> {}resp#)',
+ name.lower(), name.lower(), name.lower())
+ else:
+ f.write('-> void)\n')
+
+def genrequest(f, name, elts):
+ writeln(f, 'const {}\t: byte = {}', name, elts.get('opcode'))
+ genstruct(f, name, elts, True, False)
+ resp = elts.find('reply')
+ if resp is not None:
+ writeln(f, 'type {}seq = uint16', name.lower())
+ genstruct(f, name, resp, False, True)
+ gencallproto(f, name, elts)
+
+def genevent(f, name, elts):
+ writeln(f, 'const {} : byte = {}', name, elts.get('number'))
+ genstruct(f, name, elts, isevent=True)
+
+def geneventunion(f, mods, elts):
+ for mod in mods:
+ writeln(f, 'use "{}-gen"', mod)
+ writeln(f, 'pkg xmyrb =')
+ indent(f)
+ writeln(f, 'type event = union')
+ indent(f)
+ for (k, v) in elts.items():
+ writeln(f, '`{} {}', k, k.lower())
+ outdent(f)
+ writeln(f, ';;')
+ outdent(f)
+ writeln(f, ';;')
+
+def finddoc(doc, n):
+ for d in doc:
+ if d.get('name') == n:
+ if d.text:
+ return d.text.strip()
+
+def gendoc(f, fn, name, elt, doc):
+ indent(f)
+ fn(f, name, elt)
+ outdent(f)
+ if not doc:
+ return
+ b = doc.find('brief')
+ if b:
+ print(b.text)
+ for sub in elt:
+ if sub.tag == 'field':
+ n = sub.get('name')
+ desc = finddoc(doc, n)
+ if desc:
+ writeln(f, '`{}`: {}', n, finddoc(doc, n))
+ writeln(f, '')
+
+def generate(elts):
+ writeln(summary, 'pkg xmyrb =')
+ indent(summary)
+
+ writeln(summary, '/* enums */')
+ for (k, v) in elts['enums'].items():
+ genenum(summary, k, v)
+ gendoc(fulldoc, genenum, k, v, v.find('doc'))
+ writeln(summary, '/* structs */')
+ for (k, v) in elts['structs'].items():
+ genstruct(summary, k, v)
+ gendoc(fulldoc, genstruct, k, v, v.find('doc'))
+ writeln(summary, '/* requests */')
+ for (k, v) in elts['requests'].items():
+ genrequest(summary, k, v)
+ gendoc(fulldoc, genrequest, k, v, v.find('doc'))
+ writeln(summary, '/* unions */')
+ for (k, v) in elts['unions'].items():
+ genunion(summary, k, v)
+ gendoc(fulldoc, genunion, k, v, v.find('doc'))
+ writeln(summary, '/* events */')
+ for (k, v) in elts['events'].items():
+ genevent(summary, k, v)
+ gendoc(fulldoc, genevent, k, v, v.find('doc'))
+
+ outdent(summary)
+ writeln(summary, ';;')
+
+def addevents(events, elts):
+ for (k, v) in elts['events'].items():
+ if k in events:
+ raise Exception('event {} found in two modules'.format(k))
+ events[k] = v
+
+def main():
+ global summary, fulldoc
+ cmd = ['pkg-config', '--variable', 'xcbincludedir', 'xcb-proto']
+ xcbdir = subprocess.check_output(cmd).strip()
+ events = {}
+ for mod in sys.argv[1:]:
+ summary = io.BytesIO()
+ fulldoc = io.BytesIO()
+ indent(summary)
+ path = os.path.join(xcbdir, mod + '.xml')
+ print('generate {}...'.format(mod))
+
+ t = load(mod, path)
+ addevents(events, t)
+ generate(t)
+
+ out = '{}-doc.txt'.format(mod)
+ with open(out, 'w') as f:
+ f.write(summary.getvalue())
+ f.write(fulldoc.getvalue())
+ outdent(summary)
+ outdent(fulldoc)
+ summary.close()
+
+ summary = io.BytesIO()
+ geneventunion(summary, sys.argv[1:], events)
+ with open('events-merged.txt', 'w') as f:
+ f.write(summary.getvalue())
+
+if __name__ == '__main__':
+ main()