#!/usr/bin/python # Convert INMOS's inline documentation format to OccamDoc. # Adam Sampson import re, sys def die(*s): print >>sys.stderr, "".join(map(str, s)) sys.exit(1) def convert_file(fn): print "Parsing", fn f = open(fn) lines = [l[:-1] for l in f.readlines()] f.close() m = re.match(r'/?([^/]+)/[^/]+$', fn) if m is not None: module = m.group(1) else: module = None print "Using module", module ln = 0 # Find the end of the first comment block and insert a header comment. while ln < len(lines): l = lines[ln] if not l.startswith("--"): if module is not None: insert = ["", "--** @module %s" % (module,)] lines = lines[:ln] + insert + lines[ln:] print "Inserted header at", ln + 1 ln += len(insert) break ln += 1 # Find specification folds and convert them. while ln < len(lines): l = lines[ln] m = re.search(r'^(\s*)--{{{ specification', l) if m is None: ln += 1 continue specindent = m.group(1) print "Found spec at line", ln + 1 first = ln ln += 1 # Parse the specification fields = [] folds = 1 while 1: l = lines[ln] ln += 1 if re.search(r'^\s*--{{{', l) is not None: folds += 1 continue if re.search(r'^\s*--}}}', l) is not None: folds -= 1 if folds == 0: break continue m = re.search(r'^\s*--\s*$', l) if m is not None: fields[-1][1] += "\n" continue m = re.search(r'^\s*-- ([^ ][^:]*:| +)(.*)$', l) if m is None: die("Cannot parse spec line %d" % (ln,)) tag = m.group(1).strip() value = m.group(2).strip() if tag == "": fields[-1][1] += "\n" + value else: fields.append([tag[:-1], value]) descr = [] params = [] def do_descr(text): for para in text.split("\n\n"): descr.append(para.replace("\n", " ")) descr.append("") def do_tag(tag, value): unknown = False if tag == "Purpose": def up(m): return m.group(1).upper() value = re.sub(r'To ([a-z])', up, value) do_descr(value) elif tag == "Notes": do_descr(value) elif tag in ("Channel", "Channels", "channels"): chans = [] for l in value.split("\n"): m = re.search(r'^([^- ]*) - (.*)$', l) if m is None: chans[-1][1] += " " + l else: chans.append([m.group(1), m.group(2)]) for c, v in chans: params.append("@param %s %s" % (c, v)) elif tag == "Returned": if value.startswith("From left to right:"): rets = value.split("\n- ") for ret in rets[1:]: params.append("@return " + ret.replace("\n", " ")) else: params.append("@return " + value.replace("\n", " ")) else: unknown = True value = value.replace("\n", " ") if not unknown: pass else: tags = tag.split(", ") for tag in tags: if tag not in ("In", "Out", "In/Out"): die("Unknown tag %s in spec at %d" % (tag, first + 1)) m = re.search(r'^([^-]*) - (.*)$', value) if m is None: die("Can't find variable names in spec '%s' at %d" % (value, first + 1)) vars = m.group(1).split(", ") if len(vars) != len(tags): die("Tag/variables mismatch at %d" % (first + 1,)) params.append("@param %s %s" % (", ".join(vars), m.group(2))) for (tag, value) in fields: do_tag(tag, value) docs = descr + params # Skip blank lines before and after specification while 1: if ln >= len(lines): die("Ran off end of file after spec starting at %d" % (first + 1,)) l = lines[ln] if l.strip() != "": break ln += 1 while 1: if first < 0: die("Ran off start of file before first spec") l = lines[first - 1] if l.strip() != "": break first -= 1 lines = lines[:first] + lines[ln:] ln = first # Find the matching PROC or FUNCTION header start = ln while 1: if start < 0 or start >= len(lines): die("Didn't find header for spec at %d" % (ln + 1,)) l = lines[start] m = re.search(r'^(\s*)(PROC|.*FUNCTION)', l) if m is not None: docindent = m.group(1) break # The spec can be either inside or outside the # PROC/FUNCTION; if not indented, we assume it's # outside. if specindent == "": start += 1 else: start -= 1 if abs(ln - start) > 10: die("Header at %d is suspiciously far away from spec at %d" % (start + 1, ln + 1)) # Word-wrap the documentation width = 72 prefix = docindent + "-- " docs[0] = docindent + "--* " + docs[0] for i in range(1, len(docs)): docs[i] = prefix + docs[i] wdocs = [] def do_line(l, indent = False): if l == "": pass elif len(l) < width: wdocs.append(l) else: i = width - 1 while l[i] != " " and i > len(prefix): i -= 1 if i <= len(prefix): print "Can't break line '%s'" % (l,) wdocs.append(l) else: wdocs.append(l[:i]) p = prefix if indent: p += " " do_line(p + l[i + 1:], indent) for l in docs: do_line(l.rstrip(), l.find("-- @") != -1) lines = lines[:start] + [l.rstrip() for l in wdocs] + lines[start:] ln += len(wdocs) f = open(fn + ".odoc", "w") f.write("".join([l + "\n" for l in lines])) f.close() if __name__ == "__main__": for fn in sys.argv[1:]: convert_file(fn)