# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 Microsoft Corporation. All rights reserved.
# Released under Apache 2.0 license as described in the file LICENSE.
#
# Authors: Sebastian Ullrich, Gabriel Ebner
#

import gdb
import gdb.printing

def is_scalar(o):
    return o.cast(gdb.lookup_type('uintptr_t')) & 1 == 1

def box(n):
    return gdb.Value(n << 1 | 1).cast(gdb.lookup_type('lean_object').pointer())

def unbox(o):
    return o.cast(gdb.lookup_type('uintptr_t')) >> 1

def to_cnstr(o):
    return o.cast(gdb.lookup_type('lean_ctor_object').pointer())

def get_tag(o):
    return o['m_tag']

def get_cnstr_tag(o):
    return get_tag(o.cast(gdb.lookup_type('lean_object').pointer()))

def get_num_objs(o):
    return o['m_other']

def get_rc(o):
    return o['m_rc']

char_p = gdb.lookup_type('char').pointer()

def get_cnstr_obj_arg(o, i):
    return o.cast(gdb.lookup_type('lean_ctor_object').pointer()).dereference()['m_objs'][i]

def get_closure_arg(o, i):
    return o.cast(gdb.lookup_type('lean_closure_object').pointer()).dereference()['m_objs'][i]

def get_c_str(o):
    return o.cast(gdb.lookup_type('lean_string_object').pointer()).dereference()['m_data'] \
            .cast(gdb.lookup_type('char').pointer()).string()

class LeanObjectPrinter:
    """Print a lean_object object."""

    kinds = [
        # 244, ...
        ('ctor', []),
        ('closure', ['m_arity', 'm_fun', 'm_num_fixed']),
        ('array', ['m_size', 'm_capacity']),
        ('sarray', ['m_size', 'm_capacity']),
        ('sarray', ['m_size', 'm_capacity']),
        ('string', ['m_size', 'm_capacity', 'm_length']),
        ('mpz', ['m_value']),
        ('thunk', ['m_value', 'm_closure']),
        ('task', ['m_value', 'm_imp']),
        ('ref', ['m_value']),
        ('external', ['m_class', 'm_data']),
    ]
    lean_max_ctor_tag = 244

    def __init__(self, val):
        self.val = val.address
        if not is_scalar(self.val):
            self.kind = max(0, get_tag(self.val) - LeanObjectPrinter.lean_max_ctor_tag)

    def to_string(self):
        if is_scalar(self.val):
            return unbox(self.val)
        else:
            return "{} (RC {})".format(LeanObjectPrinter.kinds[self.kind][0], get_rc(self.val))

    def children(self):
        if is_scalar(self.val):
            return
        if self.kind >= len(LeanObjectPrinter.kinds):
            return
        typ, fields = LeanObjectPrinter.kinds[self.kind]
        val = self.val.cast(gdb.lookup_type("lean_" + typ + "_object").pointer()).dereference()
        if self.kind == 0:
            yield ('tag', get_tag(self.val))
        for f in fields:
            yield (f, val[f])
        if typ == 'ctor':
            for i in range(get_num_objs(self.val)):
                p = val['m_objs'][i].cast(gdb.lookup_type('lean_object').pointer())
                yield ('', p if is_scalar(p) else p.dereference())
        elif typ == 'array':
            for i in range(val['m_size']):
                yield ('', val['m_data'][i].cast(gdb.lookup_type('lean_object').pointer()))
        elif typ == 'closure':
            for i in range(val['m_num_fixed']):
                yield ('', val['m_objs'][i].cast(gdb.lookup_type('lean_object').pointer()))
        elif typ == 'string':
            yield ('', val['m_data'])


class LeanOptionalPrinter:
    """Print a lean::optional object."""

    def __init__(self, val):
        self.val = val

    def get_val(self):
        if hasattr(self.val, 'm_some'):
            if not self.val['m_some']:
                return None
        elif not self.val['m_value'].cast(gdb.lookup_type('char').pointer()):
            return None
        return self.val['m_value']

    def children(self):
        val = self.get_val()
        return [('', val)] if val else []

    def to_string(self):
        return str(self.val.type)

class LeanNamePrinter:
    """Print a lean::name object."""

    def __init__(self, val):
        self.val = val

    def to_string(self):
        def rec(o):
            prefix = get_cnstr_obj_arg(o, 0)
            part = get_cnstr_obj_arg(o, 1)
            s = ("'%s'" % get_c_str(part)) if get_cnstr_tag(o) == 1 else str(unbox(part))
            if not is_scalar(prefix):
                return "%s.%s" % (rec(prefix), s)
            else:
                return s

        if is_scalar(self.val['m_obj']):
            return ""
        else:
            return rec(self.val['m_obj'])

class LeanExprPrinter:
    """Print a lean::expr object."""

    expr_kinds = [
        ('bvar', [('idx', 'nat')]),
        ('fvar', ['name']),
        ('mvar', ['name', ('type', 'expr')]),
        ('sort', ['level']),
        ('const', ['name', ('levels', 'list_ref<lean::level>')]),
        ('app', [('fn', 'expr'), ('arg', 'expr')]),
        ('lam', ['name', ('type', 'expr'), ('body', 'expr')]),
        ('pi', ['name', ('type', 'expr'), ('body', 'expr')]),
        ('let', ['name', ('type', 'expr'), ('val', 'expr'), ('body', 'expr')]),
        ('lit', ['literal']),
        ('mdata', ['kvmap', 'expr']),
        ('proj', ['name', 'nat', 'expr']),
        ('elet', ['name', 'name', 'expr']),
    ]


    def __init__(self, val):
        self.val = val

    def to_string(self):
        return 'lean::expr[{}]'.format(LeanExprPrinter.expr_kinds[get_cnstr_tag(self.val['m_obj'])][0])

    def children(self):
        _, fields = LeanExprPrinter.expr_kinds[get_cnstr_tag(self.val['m_obj'])]
        for i, f in enumerate(fields):
            if isinstance(f, tuple):
                name, typ = f
            else:
                name, typ = f, f
            yield (name, get_cnstr_obj_arg(self.val['m_obj'], i).cast(gdb.lookup_type('lean::' + typ)))

class LeanLevelPrinter:
    """Print a lean::level object."""

    level_kinds = [
        ('lean::level_cell', 'zero', []),
        ('lean::level_succ', 'succ', ['m_l']),
        ('lean::level_max_core', 'max', ['m_lhs', 'm_rhs']),
        ('lean::level_max_core', 'imax', ['m_lhs', 'm_rhs']),
        ('lean::level_param_core', 'param', ['m_id']),
        ('lean::level_param_core', 'meta', ['m_id']),
    ]

    def __init__(self, val):
        self.val = val

    def to_string(self):
        kind = int(self.val['m_obj']['m_kind'])
        (subtype, name, fields) = LeanLevelPrinter.level_kinds[kind]
        subtype = gdb.lookup_type(subtype)
        val = self.val['m_obj'].cast(subtype.pointer()).dereference()
        return "({})".format(" ".join([name] + [str(val[field]) for field in fields]))

class LeanListPrinter:
    """Print a lean::list object."""

    def __init__(self, val):
        self.val = val

    def children(self):
        l = self.val
        i = 0
        while l['m_obj']:
            cell = l['m_obj'].dereference()
            yield ('[%s]' % i, cell['m_head'])
            l = cell['m_tail']
            i += 1

    def to_string(self):
        return str(self.val.type)

    def display_hint(self):
        return 'array'

class LeanBufferPrinter:
    """Print a lean::buffer object."""

    def __init__(self, val):
        self.val = val

    def children(self):
        p = self.val['m_buffer']
        for i in range(int(self.val['m_pos'])):
            yield ('[%s]' % i, p.dereference())
            p += 1

    def to_string(self):
        return str(self.val.type)

    def display_hint(self):
        return 'array'

class LeanListRefPrinter:
    """Print a lean::list_ref object."""

    def __init__(self, val):
        self.val = val

    def children(self):
        l = self.val['m_obj']
        i = 0
        while not is_scalar(l):
            yield ('[%s]' % i, get_cnstr_obj_arg(l, 0).cast(self.val.type.template_argument(0)))
            l = get_cnstr_obj_arg(l, 1)
            i += 1

    def to_string(self):
        return str(self.val.type)

    def display_hint(self):
        return 'array'

class LeanRBTreePrinter:
    """Print a lean::rb_tree object."""

    def __init__(self, val):
        self.val = val

    def children(self):
        def rec(node):
            if node['m_ptr']:
                cell = node['m_ptr'].dereference()
                for i in rec(cell['m_left']): yield i
                yield ('', cell['m_value'])
                for i in rec(cell['m_right']): yield i
        return rec(self.val['m_root'])

    def to_string(self):
        return 'lean::rb_tree' # full type is way too verbose

    def display_hint(self):
        return 'array'

class LeanRBMapPrinter:
    """Print a lean::rb_map object."""

    def __init__(self, val):
        self.val = val

    def children(self):
        for (_, child) in LeanRBTreePrinter(self.val['m_map']).children():
            yield ('', child['first'])
            yield ('', child['second'])

    def to_string(self):
        return 'lean::rb_map' # full type is way too verbose

    def display_hint(self):
        return 'map'

def build_pretty_printer():
    pp = gdb.printing.RegexpCollectionPrettyPrinter("lean")
    pp.add_printer('object', '^lean(_|::)object$', LeanObjectPrinter)
    pp.add_printer('optional', '^lean::optional', LeanOptionalPrinter)
    pp.add_printer('name', '^lean::name$', LeanNamePrinter)
    pp.add_printer('expr', '^lean::expr$', LeanExprPrinter)
    #pp.add_printer('level', '^lean::level$', LeanLevelPrinter)
    #pp.add_printer('list', '^lean::list', LeanListPrinter)
    pp.add_printer('buffer', '^lean::buffer', LeanBufferPrinter)
    pp.add_printer('list_ref', '^lean::list_ref', LeanListRefPrinter)
    pp.add_printer('rb_tree', '^lean::rb_tree', LeanRBTreePrinter)
    pp.add_printer('rb_map', '^lean::rb_map', LeanRBMapPrinter)
    return pp

def register():
    gdb.printing.register_pretty_printer(
        gdb.current_objfile(),
        build_pretty_printer(),
        replace=True)

register()
