#!/usr/bin/env python3
""" survey.py
Resolves label references and substitutes them with literal values.

>>> from io import StringIO
>>> # doctest: +REPORT_NDIFF
... print(subv.join_all(survey(StringIO('''
... == code 0x80000000
... main:
... # load 0x10010000 (UART0) into t0
... 37/u 5/rd 10010/imm20
... # store 0x48 (H) in UART0+0
... 13/i 6/rd 0/funct3 0/rs 48/imm12
... 23/s 5/rs1 0/imm12 2/funct3 6/rs2
... # store 0x65 (e) in UART0+0
... 13/i 6/rd 0/funct3 0/rs 65/imm12
... 23/s 5/rs1 0/imm12 2/funct3 6/rs2
... # store 0x6c (l) in UART0+0
... 13/i 6/rd 0/funct3 0/rs 6c/imm12
... 23/s 5/rs1 0/imm12 2/funct3 6/rs2
... # store 0x6c (l) in UART0+0
... 13/i 6/rd 0/funct3 0/rs 6c/imm12
... 23/s 5/rs1 0/imm12 2/funct3 6/rs2
... # store 0x6f (o) in UART0+0
... 13/i 6/rd 0/funct3 0/rs 6f/imm12
... 23/s 5/rs1 0/imm12 2/funct3 6/rs2
... # store 0x0a (\\\\n) in UART0+0
... 13/i 6/rd 0/funct3 0/rs a/imm12
... 23/s 5/rs1 0/imm12 2/funct3 6/rs2
... # jump back up to the top
... 6f/j 0/rd main/off21
... '''[1:-1]))))
== code 0x80000000
# main:
# load 0x10010000 (UART0) into t0
37/u 5/rd 10010/imm20
# store 0x48 (H) in UART0+0
13/i 6/rd 0/funct3 0/rs 48/imm12
23/s 5/rs1 0/imm12 2/funct3 6/rs2
# store 0x65 (e) in UART0+0
13/i 6/rd 0/funct3 0/rs 65/imm12
23/s 5/rs1 0/imm12 2/funct3 6/rs2
# store 0x6c (l) in UART0+0
13/i 6/rd 0/funct3 0/rs 6c/imm12
23/s 5/rs1 0/imm12 2/funct3 6/rs2
# store 0x6c (l) in UART0+0
13/i 6/rd 0/funct3 0/rs 6c/imm12
23/s 5/rs1 0/imm12 2/funct3 6/rs2
# store 0x6f (o) in UART0+0
13/i 6/rd 0/funct3 0/rs 6f/imm12
23/s 5/rs1 0/imm12 2/funct3 6/rs2
# store 0x0a (\\n) in UART0+0
13/i 6/rd 0/funct3 0/rs a/imm12
23/s 5/rs1 0/imm12 2/funct3 6/rs2
# jump back up to the top
6f/j 0/rd -34/imm21

>>> # doctest: +REPORT_NDIFF
... print(subv.join_all(survey(StringIO('''
... == code 0x80000000
... # repeatedly print "Hi\\\\n"
... main:
... # load 0x10010000 (UART0) into t0
... 37/u 5/rd/t0 0x10010/imm20
... # store 0x48 (H) in UART0+0
... 13/i 0/subop/add 6/rd/t1 0/rs/x0 48/imm12
... 23/s 2/subop/word 5/rs/t0 6/rs/t1 0/off12
... # store 0x69 (i) in UART0+0
... 13/i 0/subop/add 6/rd/t1 0/rs/x0 69/imm12
... 23/s 2/subop/word 5/rs/t0 6/rs/t1 0/off12
... # store 0x0a (\\\\n) in UART0+0
... 13/i 0/subop/add 6/rd/t1 0/rs/x0 0a/imm12
... 23/s 2/subop/word 5/rs/t0 6/rs/t1 0/off12
... 17/u 7/rd/t2 main/off20hi
... 67/i 0/subop 0/rd/x0 7/rs/t2 main+4/off12lo
... '''[1:-1]))))
== code 0x80000000
# repeatedly print "Hi\\n"
# main:
# load 0x10010000 (UART0) into t0
37/u 5/rd/t0 10010/imm20
# store 0x48 (H) in UART0+0
13/i 0/subop/add 6/rd/t1 0/rs/x0 48/imm12
23/s 2/subop/word 5/rs/t0 6/rs/t1 0/off12
# store 0x69 (i) in UART0+0
13/i 0/subop/add 6/rd/t1 0/rs/x0 69/imm12
23/s 2/subop/word 5/rs/t0 6/rs/t1 0/off12
# store 0x0a (\\n) in UART0+0
13/i 0/subop/add 6/rd/t1 0/rs/x0 a/imm12
23/s 2/subop/word 5/rs/t0 6/rs/t1 0/off12
17/u 7/rd/t2 0/imm20
67/i 0/subop 0/rd/x0 7/rs/t2 -1c/imm12
"""
import subv
import bits


def observe(word, pc, map):
    """resolve a label reference.

    >>> observe((3, '3'), 0, {})
    (3, '3')
    >>> subv.format_part(observe(('label', 'imm32'), 0, {'label': 0x1234}))
    '1234/imm32'
    >>> subv.format_part(observe(('label-2', 'imm20',), 0, {'label': 0x1234}))
    '1232/imm20'
    >>> subv.format_part(observe(('label', 'off32'), 0x1000, {'label': 0x1234}))
    '234/imm32'
    >>> subv.format_part(observe(('label+4', 'off32'), 0x1000, {'label': 0x1234}))
    '238/imm32'
    >>> subv.format_part(observe(('label', 'off32'), 0x1234, {'label': 0x1000}))
    '-234/imm32'
    >>> subv.format_part(observe(('label', 'off20hi'), 0x1000, {'label': 0x3234}))
    '2/imm20'
    >>> subv.format_part(observe(('label', 'off12lo'), 0x1000, {'label': 0x3234}))
    '234/imm12'
    >>> subv.format_part(observe(('label', 'off20hi'), 0x3234, {'label': 0x1000}))
    '-2/imm20'
    >>> subv.format_part(observe(('label', 'off12lo'), 0x3234, {'label': 0x1000}))
    '-234/imm12'

    >>> observe(('label', 'imm32'), 0, {})
    Traceback (most recent call last):
        ...
    ValueError: undefined label 'label'
    >>> observe(('label', 'imm8'), 0, {'label': 0x1234})
    Traceback (most recent call last):
        ...
    ValueError: label reference out of range (8 bit): 'label' (0x1234)
    """
    if not subv.is_reference(word):
        return word

    ref = subv.parse_reference(word)

    if ref["label"] not in map:
        raise ValueError("undefined label '{}'".format(ref["label"]))

    value = map[ref["label"]] + ref["offset"]

    if ref["mode"] == "off":
        value -= pc

    if "split" in ref:
        sgn = -1 if value < 0 else 1
        value *= sgn

        if ref["split"] == "hi" and ref["size"] == 20:
            value = value >> 12
        elif ref["split"] == "lo" and ref["size"] == 12:
            value = value & 0xFFF
        else:
            raise ValueError(
                "unknown label split mode {mode}{size}{split}".format(**ref)
            )

        value *= sgn

    if value > 0:
        if value >= (1 << ref["size"]):
            raise ValueError(
                "label reference out of range ({size} bit): '{label}' (0x{:x})".format(
                    value, **ref
                )
            )
    else:
        if value < -(1 << (ref["size"] - 1)):
            raise ValueError(
                "label reference out of range ({size} bit): '{label}' (-0x{:x})".format(
                    abs(value), **ref
                )
            )

    return (value, "imm{size}".format(**ref))


@subv.with_parsed_lines
def survey(iter):
    queue = []
    map = {}
    addr, bitcount = -1, 0
    for segment, line in iter:
        line["addr"] = addr
        queue.append(line)

        # step forward addr
        type = line["type"]
        if type == "segment":
            addr, bitcount = line["segment"][1], 0
        elif type == "label":
            assert bitcount == 0, "label isn't byte aligned"
            map[line["label"]] = addr
        elif type == "instr":
            if len(line["instr"]) == 0:
                continue

            if segment == "data":
                for part in line["instr"]:
                    bitcount += int(part[1])

                if bitcount >= 8:
                    addr += bitcount // 8
                    bitcount = bitcount % 8
            elif segment == "code":
                assert (
                    bitcount == 0
                ), "instruction isn't byte aligned ({} bitcount left)".format(
                    8 - bitcount
                )
                assert addr % 2 == 0, "instruction isn't 2-byte aligned"

                op = line["instr"][0]
                instr_size = 0
                if op[1] in "usirjb":
                    addr += 4
                else:
                    raise ValueError("Unknown instruction format '{}'".format(op[1]))
            else:
                raise ValueError("unknown segment '{}'".format(segment))

    for i, line in enumerate(queue, start=1):
        try:
            type = line["type"]
            if type == "instr":
                instr = []
                for part in line[type]:
                    instr.append(observe(part, line["addr"], map))
                line[type] = instr
                yield subv.format(line)
            elif type == "label":
                yield "# {}".format(line["raw"])
            else:
                yield line["raw"]
        except Exception as e:
            msg = "failed to survey line"
            msg = msg + "\n{}:{}:  {}".format("(unknown)", i, subv.format(line))
            msg = msg + "\nparsed as {}".format(subv.dump(line))
            raise subv.SubVException(msg) from e


if __name__ == "__main__":
    import sys

    for line in survey(sys.stdin):
        print(line)
