#! /usr/bin/env python
#
# Converts the output of hilti-doc into a series of reST documents for
# the HILTI and Spicy documentatin. Note that it will overwrite existing
# files in the
# output directory.

import os.path
import sys

import ConfigParser

def canonicalizeText(doc, indent=4):

    doc = doc.replace("\\n", "\n")

    if not doc:
        return ""

    canon = []

    for line in doc.strip().split("\n"):
        line = line.strip()
        line = " " * indent + line

        canon += [line]

    return "\n".join(canon)

def getDoc(section, key):
    if Doc.has_option(section, key):
        return Doc.get(section, key)
    else:
        return ""

def hiltiInstruction(out, instr):
    ns = getDoc(instr, "namespace")
    cls = getDoc(instr, "class")
    mnemo = getDoc(instr, "mnemonic")

    ops_brief = ""
    ops_long = ""

    for (b, l) in ( ("target", "Target"), ("op1", "Operator 1"), ("op2", "Operator 2"), ("op3", "Operator 3") ):
        type = getDoc(instr, "type_%s" % b)
        default = getDoc(instr, "default_%s" % b)

        if type:
            ops_brief += " " + b
            ops_long += "    :%s: %s" % (l, type)

            if default:
                ops_long += " [default: %s]" % default

            ops_long += "\n"

    print >>out
    print >>out, ".. hlt:instruction:: %s::%s %s%s" % (ns, cls, mnemo, ops_brief)
    print >>out
    print >>out, ops_long
    print >>out
    print >>out, canonicalizeText(getDoc(instr, "description"))
    print >>out

def fmtSpicyOperator(op):
    kind = getDoc(op, "kind")
    op1 = "t:%s" % getDoc(op, "type_op1").replace("<*>", "")
    op2 = "t:%s" % getDoc(op, "type_op2").replace("<*>", "")
    op3 = "t:%s" % getDoc(op, "type_op3").replace("<*>", "")

    if kind == "Add":
        return ("add", "op:add %s op:[ %s op:]" % (op1, op2))

    if kind == "Attribute":
        return ("attribute", "%s op:. a:<attr>" % op1)

    if kind == "AttributeAssign":
        return ("attribute assign", "%s op:. a:<attr> = %s" % (op1, op3))

    if kind == "BitAnd":
        return ("&", "%s <sp> op:& <sp> %s" % (op1, op2))

    if kind == "BitOr":
        return ("|", "%s <sp> op:| <sp> %s" % (op1, op2))

    if kind == "BitXor":
        return ("^", "%s <sp> op:^ <sp> %s" % (op1, op2))

    if kind == "Call":
        return ("()", "a:%s ( %s )" % (op1, op2))

    if kind == "Coerce":
        return ("coerce", "%s <sp> x:coerces-to <sp>  %s" % (op1, op2))

    if kind == "Cast":
        return ("cast", "op:cast< %s op:>( %s op:)" % (op2, op1))

    if kind == "DecrPostfix":
        return ("--", "%s op:--" % op1)

    if kind == "DecrPrefix":
        return ("--", "op:-- %s" % op1)

    if kind == "Delete":
        return ("delete", "op:delete %s op:[ %s op:]" % (op1, op2))

    if kind == "Deref":
        return ("*", "op:\* %s" % op1)

    if kind == "Div":
        return ("/", "%s <sp> op:/ <sp>  %s" % (op1, op2))

    if kind == "Equal":
        return ("==", "%s <sp> op:== <sp> %s" % (op1, op2))

    if kind == "Greater":
        return (">", "%s <sp> op:> <sp>  %s" % (op1, op2))

    if kind == "HasAttribute":
        return ("has attribute", "%s ?. a:<attr>" % op1)

    if kind == "TryAttribute":
        return ("try attribute", "%s .? a:<attr>" % op1)

    if kind == "IncrPostfix":
        return ("++", "%s op:++" % op1)

    if kind == "IncrPrefix":
        return ("++", "op:-- %s" % op1)

    if kind == "Index":
        return ("[]", "%s op:[ %s op:]" % (op1, op2))

    if kind == "In":
        return ("in", "%s <sp> op:in <sp> %s" % (op1, op2))

    if kind == "IndexAssign":
        return ("[]=", "%s op:[ %s op:] op:= %s" % (op1, op2, op3))

    if kind == "LogicalAnd":
        return ("&&", "%s <sp> op:&& <sp>  %s" % (op1, op2))

    if kind == "LogicalOr":
        return ("||", "%s <sp> op:|| <sp>  %s" % (op1, op2))

    if kind == "Lower":
        return ("<", "%s <sp> op:< <sp>  %s" % (op1, op2))

    if kind == "Minus":
        return ("-", "%s <sp> op:- <sp>  %s" % (op1, op2))

    if kind == "Mod":
        return ("mod", "%s <sp> op:mod <sp>  %s" % (op1, op2))

    if kind == "Mult":
        return ("*", "%s <sp> op:\* <sp>  %s" % (op1, op2))

    if kind == "New":
        return ("new", "op:new %s" % op1)

    if kind == "Not":
        return ("!", "op:! <sp> %s" % op1)

    if kind == "Plus":
        return ("+", "%s <sp> op:+ <sp>  %s" % (op1, op2))

    if kind == "PlusAssign":
        return ("+=", "%s <sp> op:+= <sp>  %s" % (op1, op2))

    if kind == "MinusAssign":
        return ("-=", "%s <sp> op:+= <sp>  %s" % (op1, op2))

    if kind == "Power":
        return ("**", "%s <sp> op:\*\* <sp>  %s" % (op1, op2))

    if kind == "ShiftLeft":
        return ("<<", "%s <sp> op:<< <sp>  %s" % (op1, op2))

    if kind == "ShiftRight":
        return (">>", "%s <sp> op:>> <sp>  %s" % (op1, op2))

    if kind == "SignNeg":
        return ("-", "op:- <sp> %s" % op1)

    if kind == "SignPos":
        return ("+", "op:+ <sp> %s" % op1)

    if kind == "Size":
        return ("size", "op:| %s op:|" % op1)

    print >>sys.stderr, "unknown spicy operator", kind
    sys.exit(1)

def spicyOperator(out, op):
    (symbol, sig) = fmtSpicyOperator(op)

    if not symbol:
        return

    ns = getDoc(op, "namespace")
    kind = getDoc(op, "kind")

    print >>out, ".. spicy:operator:: %s::%s %s" % (ns, kind, sig)
    print >>out
    print >>out, canonicalizeText(getDoc(op, "description"))
    print >>out

def spicyMethod(out, op):
    args = []

    for i in range(5):
        arg = getDoc(op, "type_callarg%s" % i).replace("<*>", "")
        if arg:
            args += [arg]

    method = getDoc(op, "type_op2")

    if method.startswith("<"):
        method = method[1:]

    if method.endswith(">"):
        method = method[:-1]

    ns = getDoc(op, "namespace")
    kind = getDoc(op, "kind")
    result = getDoc(op, "type_result")

    if not result:
        result = "-"

    print >>out, ".. spicy:method:: %s::%s %s %s (%s)" % (ns, method, result, method, ", ".join(args))
    print >>out
    print >>out, canonicalizeText(getDoc(op, "description"))
    print >>out

if len(sys.argv) != 2:
    print "usage: hilti-doc | doc-to-rst <output dir>"
    sys.exit(1)

OutputDir = sys.argv[1]
Doc = ConfigParser.RawConfigParser()
Doc.read('/dev/stdin')

hiltiGroups = {}
spicyGroups = {}

for section in Doc.sections():
    if section.startswith("hilti-instruction:"):
        idx = getDoc(section, "mnemonic")
        group = getDoc(section, "namespace")

        if not group in hiltiGroups:
            hiltiGroups[group] = {}

        if not idx in hiltiGroups[group]:
            hiltiGroups[group][idx] = []

        hiltiGroups[group][idx] += [section]

    if section.startswith("spicy-operator:"):
        idx = getDoc(section, "kind")
        group = getDoc(section, "namespace")

        if group.endswith("_"):
            group = group[:-1]

        if not group in spicyGroups:
            spicyGroups[group] = {}

        if not idx in spicyGroups[group]:
            spicyGroups[group][idx] = []

        spicyGroups[group][idx] += [section]

for group in hiltiGroups.keys():

    out = open(os.path.join(OutputDir, "hilti-instr-%s.rst" % group), "w")

    for idx in sorted(hiltiGroups[group].keys()):
        for section in hiltiGroups[group][idx]:
            hiltiInstruction(out, section)

    out.close()

for group in spicyGroups.keys():

    out = open(os.path.join(OutputDir, "spicy-%s.rst" % group), "w")
    print >>out
    print >>out, ".. rubric:: Operators"
    print >>out

    cnt = 0

    for idx in sorted(spicyGroups[group].keys()):
        if idx != "MethodCall":
            for method in spicyGroups[group][idx]:
                spicyOperator(out, method)
                cnt += 1

    if not cnt:
        print >>out, "None defined."

    print >>out
    print >>out, ".. rubric:: Methods"
    print >>out

    cnt = 0

    for idx in sorted(spicyGroups[group].keys()):
        if idx == "MethodCall":
            for method in spicyGroups[group][idx]:
                spicyMethod(out, method)
                cnt += 1

    if not cnt:
        print >>out, "None defined."

    out.close()
